Showing posts with label okky. Show all posts
Showing posts with label okky. Show all posts

Sunday, September 18, 2016

Oracle Fusion Middleware Enterprise Deployment Guide 11g vs 12c

Pada tahun 2013, saya mulai mengaplikasikan Oracle Enterprise Deployment Guide [1][2][3][4] untuk beberapa produk Oracle Fusion Middleware 11g di salah satu perusahaan telekomunikasi di Indonesia dan di salah satu badan pemerintahan DKI Jakarta. Pada saat itu saya berkesempatan mengimplementasikan topologi high availability yang direkomendasikan Oracle untuk kali pertamanya pada produk Oracle SOA Suite, Oracle WebCenter Suite, dan Oracle Access Management. Cukup berat pertama kali mempelajarinya karena jumlah halaman yang mencapai ratusan dan navigasi khas Oracle yang "lompat-lompat", namun kondisi obsessive compulsive disorder (OCD) yang saya miliki membuat saya terus ingin berusaha mempelajari setiap halaman dari guide tersebut dan mencoba untuk mengimplementasikannya dengan benar. Ketika salah satu dokumentasi sudah dipahami, ternyata dokumentasi untuk produk yang lain tidak jauh berbeda polanya sehingga dapat lebih cepat dipahami.

Kurang lebih 3 tahun telah berlalu, dan kali ini saya diberi kesempatan lagi untuk mengimplementasikan high availability architecture pada produk Oracle SOA Suite dan Oracle WebCenter Suite namun kali ini di rilisnya yang terbaru yaitu rilis 12c di salah satu instansi pemerintah yang besar. Kebetulan dokumentasi untuk rilis 12c juga sudah terbit bersamaan dengan jalannya project. Walaupun ada juga seperti WebCenter Portal yang statusnya masih beta dokumentasinya. Tidak seperti 3 tahun  yang lalu, kali ini saya merasa lebih mudah untuk mempelajari dokumentasinya. Saya melihat ada beberapa perkembangan dari sisi teknologi dan konvensi direktori yang digunakan. Berikut ini beberapa perbedaan yang cukup signifikan yang saya amati di antara kedua rilis, yang akan saya coba jelaskan detilnya di paragraf-paragraf selanjutnya:

  1. Directory conventions
  2. Coherence configuration
  3. Web Tier standalone domain
  4. Simplified RCU
  5. Server groups
  6. Virtual IP
  7. Per ost NodeManager
  8. WSM Policy Manager cluster

Directory Conventions


Baik rilis 11g maupun rilis 12c, Oracle menerapkan pemisahan antara ASERVER_HOME dengan MSERVER_HOME. Namun pada rilis 11g konvensi direktori yang diberlakukan cukup kompleks dan membingungkan terutama untuk implementor yang baru pertama kali mengimplementasikan topologi high availability. Pada 11g lokasi konfigurasi domain untuk AdminServer (ASERVER_HOME) berada di:

 /u01/app/oracle/admin/domain_name/aserver/domain_name

Di sini ada 2 kali pengulangan nama domain yang sangat membingungkan. Sedangkan untuk konfigurasi domain untuk Managed Server (MSERVER_HOME) berada di:

/u02/app/oracle/admin/domain_name/mserver/domain_name

Jika dibandingkan dengan konvensi pada rilis 12c, lokasi direktori menjadi lebih mudah dan singkat, yaitu ASERVER_HOME menjadi:

/u01/oracle/config/domains/domain_name

dan MSERVER_HOME menjadi:

/u02/oracle/config/domains/domain_name 

Selain itu konvensi untuk lokasi lainnya pada rilis 12c secara umum lebih mudah dan lebih logis untuk diimplemementasikan.

Coherence Configuration


Pada rilis 11g, deployment SOA Composite terkadang sangat bergantung pada benar tidaknya konfigurasi Coherence yang perlu dilakukan pada server startup argument menggunakan tangosol.coherence.wka<n>. Argumen tersebut perlu ditambahkan di setiap Managed Server yang termasuk di dalam SOA cluster. Pada rilis 12c, ketika initial domain creation secara otomatis akan terbuat sebuah defaultCoherenceCluster yang akan menangani hal ini. Sehingga tidak diperlukan lagi penambahan argumen Tangosol seperti dulu.

Web Tier Role and Standalone Domain


Pada rilis 11g, saya dulu sempat mempertanyakan sebetulnya fungsi dari Web Tier (Oracle HTTP Server) ini apa selain untuk mengakomodasi Single Sign-On (SSO) solution dari Oracle Access Management. Ternyata di dokumentasi rilis 12c lebih dijelaskan fungsi dasar dari Web Tier ini apa dan bagaimana opsional-nya untuk mendeploy-nya di high availability topology. Menurut saya Web Tier ini tetap diperlukan untuk reverse proxy dan mempermudah deployment ketika nantinya akan diintegrasikan dengan load balancer, karena cukup 1 rule saja yang perlu didaftarkan ke load balancer yaitu ke Web Tier.

Selain itu pada rilis 12c ini, memungkinkan untuk memisahkan deployment dari Web Tier menjadi domain yang terpisah dari WebLogic Domain yang menjadi target reverse proxy. Keuntungan dari pendekatan ini adalah kita dapat menempatkan konfigurasi Web Tier di mesin yang terpisah menjauhi application tier dan bisa digunakan untuk me-reverse proxy lebih dari 1 WebLogic Domain.

Simplified RCU


Repository Creation Utility pada rilis 11g merupakan komponen yang di-download secara terpisah dari installer yang lainnya. Namun pada rilis 12c merupakan kesatuan dari produk yang akan diinstall. Misalnya ketika akan meng-install Oracle SOA Suite, maka RCU-nya yang diperlukan sudah termasuk di dalam folder soa. Pendekatan ini memudahkan komponen yang harus di-download dan juga memudahkan proses instalasi. Selain itu ketika akan membuat skema database, RCU 12c membuat sebuah skema khusus yang berisi metadata RCU, biasanya memiliki sufiks STB. Skema STB ini memiliki peran untuk menyimpan informasi skema-skema lainnya yang memiliki prefiks yang sama. Sehingga ketika proses pembuatan database Middleware tidak perlu lagi sysadmin mengetikkan satu per satu prefiks dan password-nya seperti pada rilis 11g.

Server Groups


Salah satu aspek yang paling kompleks ketika mengkonfigurasi high availability topology adalah pada bagian custom deployment target, misalnya ketika ingin memisahkan WSM pada cluster tersendiri. Pada 12c proses tersebut menjadi dimudahkan dengan adanya fitur Server Groups. Di mana Server Groups ini merupakan deployment set yang dapat di-deploy secara kesatuan dan merepresentasikan sebuah fungsi. Pendekatan ini dapat mengurangi tingkat error yang dilakukan ketika memilih deployment yang akan ditargetkan.

Virtual IP


Virtual IP digunakan untuk mem-failover sebuah proses seperti misalnya proses AdminServer dari node yang aktif ke node yang pasif karena kegagalan node aktif. Selain itu virtual IP pada rilis 11g juga dimanfaatkan untuk proses whole server migration yang bisa dilakukan oleh Oracle SOA server. Sehingga WLS_SOA1 misalnya dapat sepenuhnya pindah ke SOAHOST2, ketika SOAHOST1 gagal. Sehingga pada suatu ketika SOAHOST2 berisi WLS_SOA1 dan WLS_SOA2.

Pada rilis 12c virtual IP yang diperlukan hanya untuk AdminServer, karena Oracle SOA Suite 12c sudah mendukung fitur Automatic Service Migration, di mana yang dipindahkan adalah pada level service-nya. Sehingga migrasi penuh WebLogic Managed Server dari SOAHOST1 ke SOAHOST2 tidak diperlukan.

Per Host Node Manager


Pada rilis 11g, tidak secara detil dijelaskan bagaimana sebenarnya konfigurasi yang benar untuk NodeManager. Penempatan direktori dan perintah yang digunakan juga masih tidak jelas. Hingga akhirnya waktu itu saya mencari-cari cara sendiri untuk mempermudah proses startup dan shutdown dari NodeManager dari berbagai sumber hingga menghasilkan how to versi saya yang saya sempat share di [5].

Pada rilis 12c, walaupun masih belum jelas seperti yang saya harapkan. Namun setidaknya sudah terlihat bahwa ada 2 alternatif konfigurasi yaitu per domain NodeManager atau per host NodeManager. Masing-masing alternatif dijelaskan keuntungan dan kekurangannya, namun masih disarankan untuk membuat per host NodeManager.

WSM Policy Manager Cluster


Sejak rilis 11g saya sudah mempertanyakan alasan sebetulnya kenapa WSM Policy Manager dipisahkan ke 1 cluster khusus, yaitu WSM-PM_Cluster. Saya sempat mempertanyakan juga ke Oracle Support mengenai hal ini, namun jawaban yang mereka berikan tidak memuaskan. Pada rilis 12c dijelaskan secara lebih detil bahwa dengan alasan kinerja secara keseluruhan lebih baik jika WSM Policy Manager dipisahkan deployment-nya ke cluster khusus.


Demikian sedikit ulasan saya mengenai dokumentasi Oracle. Semoga bermanfaat.


Daftar referensi:
[1] Fusion Middleware Enterprise Deployment Guide for Oracle SOA Suite
[2] Fusion Middleware Enterprise Deployment Guide for Oracle WebCenter Portal
[3] Fusion Middleware Enterprise Deployment Guide for Oracle WebCenter Content
[4] Fusion Middleware Enterprise Deployment Guide for Oracle Identity Management
[5] Simplified NodeManager Setup

Optimal Memory Partitioning with Linux Explicit Huge Pages

Salah satu tantangan yang saya rasakan ketika membuat sebuah platform virtualisasi adalah bagaimana secara efektif platform tersebut dapat mengalokasikan memori untuk virtual machine secara fleksibel dan handal. Seringkali ketika kita tidak memikirkan hal tersebut, memori yang tersedia di hypervisor tidak selalu sepenuhnya tersedia secara efektif. Ketika memori diperlukan untuk menghidupkan sebuah virtual machine, bisa jadi memori yang tersedia sudah dipegang oleh mekanisme Linux Page Cache atau proses di hypervisor yang lain. Sehingga memori yang bebas digunakan oleh virtual machine sulit untuk diperoleh angka persisnya berapa. Terkadang kita tidak menyadari bahwa sebetulnya memori yang bebas itu sudah menipis sehingga ketika menghidupkan virtual machine tambahan, hypervisor harus melakukan swapping sehingga kinerja keseluruhan menjadi turun.

Hypervisor pun sebetulnya juga membutuhkan resource komputasi yang cukup untuk melakukan aktivitasnya. Sebuah hypervisor berkapasitas 128 GB memori, tentu bukan berarti kita dapat menghidupkan 16 virtual machine berkapasitas 8 GB. Kita harus memikirkan juga untuk menyisakan sebagian memori dari 128 GB tersebut khusus untuk dikonsumsi oleh hyperivisor. Pendekatan yang dilakukan oleh cloud provider besar seperti Google Cloud Platform dan Microsoft Azure, adalah dengan menyediakan paket-paket sizing virtual machine yang sedikit dikurangi kapasitas memorinya dari kapasitas yang umum. 

Skema Sizing Microsoft Azure

Pada skema Microsoft Azure di atas, terlihat bahwa untuk setiap tipe virtual machine yang ditawarkan besar kapasitas memori adalah sedikit di bawah angka standar dari deret eksponensial 2 (1 GB, 2 GB, 4 GB, 8 GB, 16 GB, 32 GB, 64 GB, 128 GB). Semakin besar memori yang akan dialokasikan ke sebuah virtual machine, semakin besar pula selisihnya dengan angka umum.

Skema Sizing Google Cloud Platform

Skema yang serupa ternyata juga terlihat pada skema ukuran virtual machine yang disediakan di Google Cloud Platform. Walaupun tidak seagresif pengalokasian pada Microsoft Azure, Google Cloud Platform juga terlihat menyediakan sebagian memori yang seharusnya dialokasikan mengikuti deret eksponensial 2.

Kedua contoh tersebut dapat diaplikasikan pada cloud provider besar seperti Microsoft Azure dan Google Cloud Platform karena jumlah hypervisor node yang mereka miliki juga sangat banyak. Namun bagaimana pada kasus topologi yang sederhana. Misalnya saja pada server development di Nostratech, bernama Arjuna, yang hanya memiliki kapasitas memori 96 GB. Pengalokasian seperti model tersebut selain cukup menyulitkan dalam perhitungan, dalam jumlah virtual machine yang banyak juga akan sangat banyak memori yang tidak termanfaatkan dengan baik.

Sebagai latar belakang, Arjuna merupakan development platform di Nostratech yang saya rancang dengan menggunakan teknologi Linux KVM sebagai hypervisor dan OpenZFS sebagai storage management layer-nya. Kedua teknologi tersebut bisa dikatakan banyak menggunakan memori sehingga saya merasa cukup tertantang ketika merancang Arjuna. Saya tidak pernah suka dengan swap space, karena menurut saya ketika sudah terjadi swapping proses apapun juga akan menjadi lambat dan tidak efektif lagi. Sehingga saya tidak mengalokasikan swap space sama sekali pada Arjuna. Berikut adalah pembagian memori yang saya lakukan pada Arjuna:


Pada skema tersebut terlihat dengan jelas pembagian 96 GB memori ke masing-masing pemetaannya. Linux Page Cache merupakan mekanisme caching pada Linux dan untuk mengaturnya membutuhkan parameter yang cukup kompleks dan tidak secara eksplisit terlihat berapa penggunakan maksimumnya. Untuk itulah pendekatan yang saya lakukan terbalik. Saya merancang untuk mengalokasikan 8 GB untuk OpenZFS ARC caching, dan 80 GB untuk virtual machine. Sehingga sisa 8 GB dapat secara bebas dimanfaatkan oleh Linux Page Cache. Namun bagaimana saya dapat menjamin saya dapat mengalokasikan 80 GB untuk virtual machine? Di sinilah saya memanfaatkan teknologi explicit huge pages pada Linux.

Linux mengakses memori dalam bentuk satuan kecil yang bernama pages. Besaran asli dari pages adalah sekitar 4 kB. Huge pages [1][2] merupakan terobosan selanjutnya yang memungkinkan besaran dari pages ditingkatkan menjadi 2 MB hingga 1 GB. Sehingga pengalokasian memori dapat lebih efektif karena jumlah call yang diperlukan menjadi lebih sedikit untuk jumlah pengalokasian memori yang sama.

Linux memiliki 2 tipe huge pages, transparent huge pages dan explicit huge pages. Perbedaan dari keduanya adalah pada transparent huge pages kernel Linux sendiri yang melakukan pengaturan proses apa dan menggunakan page size yang mana yang tepat. Sedangkan pada explicit huge pages sejak dari awal boot, kernel sudah melakukan alokasi partisi memori sehingga menjamin keseinambungan dari memori yang dialokasikan. Arjuna menggunakan explicit huge pages, sehingga setiap kali boot Arjuna memiliki pemetaan memori seperti gambar sebelumnya.

Untuk konfigurasi yang perlu dilakukan adalah cukup dengan menambahkan kernel boot parameter berikut ini:
transparent_hugepage=never default_hugepagesz=2M hugepagesz=2M hugepages=40960

Sedangkan untuk melihat berapa sisa kapasitas memori dari partisi explicit huge pages dapat menggunakan perintah berikut ini:
grep Huge /proc/meminfo

Agar Linux KVM dapat memanfaatkan partisi memori huge pages tersebut, perlu disisipkan sedikit konfigurasi pada QEMU command line-nya:
-mem-path /dev/hugepages

Jika menggunakan libvirt XML, maka perlu menambahkan:
<memoryBacking>
<hugepages/>
</memoryBacking>

Selamat mencoba!

[1] https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt
[2] https://wiki.debian.org/Hugepages




Wednesday, June 15, 2016

Alternative Backend Mapping in HAProxy

HAProxy memiliki fitur Access Control List (ACL) [1] yang sangat bermanfaat dalam menentukan backend mana yang akan dilewatkan sebuah request yang masuk ke HAProxy. Kriterium yang selama ini paling saya gunakan adalah hdr(host), di mana akan sangat bermanfaat terutama jika disinergikan dengan fitur server block dari nginx[2]. Misalnya untuk me-load-balanced 1 atau lebih nginx cluster yang memiliki banyak server name, kita cukup mendefinisikan sebuah frontend proxy dengan banyak kombinasi acl, hdr(host), dan use_backend sehingga request yang masuk dapat diteruskan ke backend proxy yang tepat sesuai dengan keinginan kita.

Berikut adalah contoh konfigurasinya:
# /etc/haproxy/haproxy.cfg
...
frontend https-endpoint
 bind *:443 ssl crt /etc/ssl/somedomain.com/STAR_somedomain_com.pem
 mode http
 acl WWW_REQUESTS hdr(host) -i www.somedomain.com
 acl DEV_REQUESTS hdr(host) -i dev.somedomain.com
 acl FIN_REQUESTS hdr(host) -i fin.somedomain.com
 acl CS_REQUESTS hdr(host) -i cs.somedomain.com
 acl API_REQUESTS hdr(host) -i api.somedomain.com
 use_backend www-nodes if WWW_REQUESTS
 use_backend dev-nodes if DEV_REQUESTS
 use_backend fin-nodes if FIN_REQUESTS
 use_backend cs-nodes if CS_REQUESTS
 use_backend api-nodes if API_REQUESTS
 default_backend dummy-nodes
...

Walaupun metode ini sangat powerful untuk kebutuhan seperti ini, namun menggunakan metode ini memiliki kekurangan terutama jika jumlah hostname yang perlu dievaluasi cukup banyak. Terbayang sebuah frontend proxy section yang memiliki jumlah baris yang banyak untuk mengevaluasi 1000 hostname misalnya. Untungnya HAProxy memiliki metode lain untuk menyelesaikan permasalahan ini. HAProxy mulai memperkenalkan fitur map sejak versi 1.5 [3], di mana HAProxy dapat membaca file selain konfigurasi HAProxy untuk menentukan backend mana yang perlu dijalankan terhadap sebuah hostname tertentu.

Format file yang perlu dibuat untuk proses mapping ini sangat sederhana, yaitu 2 kolom yang dipisahkan dengan whitespace. Kolom pertama merupakan kolom untuk kemungkinan nilai yang akan ada, sementara kolom kedua merupakan nama backend proxy section yang akan dijalankan ketika nilai yang telah dievaluasi dari frontend cocok dengan kolom pertama.

Jika menggunakan contoh sebelumnya, kurang lebih isi dari map file tersebut adalah seperti ini:
# /etc/haproxy/maps.d/20-somedomain.map
...
#domain name   #backend name
www.somedomain.com  www-nodes
dev.somedomain.com  dev-nodes
fin.somedomain.com  fin-nodes
cs.somedomain.com  cs-nodes
api.somedomain.com  api-nodes
...

Kemudian muncul pertanyaan, bagaimana menutupi kebutuhan untuk default_backend? Jawabannya ada di format HAProxy map-nya itu sendiri di konfigurasi utama HAProxy. Berikut adalah konfigurasi HAProxy yang baru dengan menggunakan HAProxy map:
# /etc/haproxy/haproxy.cfg
...
frontend somedomain_https-endpoint
 bind *:443 ssl crt /etc/ssl/somedomain.com/STAR_somedomain_com.pem
 mode http
 use_backend %[req.hdr(host),lower,map_dom(/etc/haproxy/maps.d/20-somedomain.map,generic_dummy-nodes)]
...

Konfigurasi menjadi lebih sederhana dan singkat, sehingga konfigurasi juga lebih terbaca. Selain itu jika kita perhatikan lebih seksama, format HAProxy map ini generik dan dapat digunakan untuk fungsi lain selain untuk membaca kombinasi hostname request dan backend routing. Misalnya untuk menggantikan fungsi evaluasi ACL untuk prefiks dari web site dan lain-lain. Saya mulai menggunakan konfigurasi ini untuk infrastruktur di kantor dan sejauh ini berjalan sangat stabil.

Sekian artikel saya kali ini, selamat mencoba ya! :)

Daftar Referensi:

Tuesday, June 14, 2016

Managing Multiple Configuration Files in HAProxy

Selama ini dalam mengkonfigurasi HAProxy di project-project yang saya jalani, saya tidak pernah terpikir untuk memecah file konfigurasi menjadi beberapa file konfigurasi yang kecil. Biasanya konfigurasi yang diperlukan cukup singkat dan terfokus pada aplikasi backend tertentu yang perlu di-load-balanced. Namun ketika saya mulai mengkonfigurasi HAProxy untuk infrastruktur development kantor, saya mulai terbentur kebutuhan ini. Kebutuhan ini ada karena dalam infrastruktur development kantor terdiri dari beberapa project deployment environment yang sedang berjalan bersamaan. Agar efisien secara penggunaan resource, HAProxy yang digunakan cukup menggunakan 2 node HAProxy yang sifatnya global terhadap semua project yang ada.

Berangkat dari kebutuhan tersebut, saya kemudian mencari informasi bagaimana caranya memecah file konfigurasi HAProxy menjadi beberapa file konfigurasi kecil. Dengan pendekatan ini, konfigurasi dapat diatur lebih spesifik per project, membacanya juga lebih mudah, termasuk jika saya perlu men-disable konfigurasi tertentu dengan cepat saya cukup me-rename file konfigurasi yang ingin saya disable dan me-restart HAProxy-nya.

Willy Tarreau, pengembang utama HAProxy, menyatakan dalam salah satu entry di mailing list HAProxy [1], bahwa cara untuk memecah konfigurasi adalah dengan menggunakan parameter -f config.cfg secara berulang-ulang di command line argument-nya HAProxy. Pendekatan ini belum sempat saya lakukan, karena saya melihat ketidakpraktisan dalam operasionalnya. Perubahan init script Linux, seperti upstart, systemd, atau init system lainnya, untuk HAProxy perlu dilakukan untuk menambahkan parameter -f config.cfg. Perubahan init script Linux tersebut juga perlu dilakukan baik ketika ada penambahan file konfigurasi baru, maupun ketika ada keperluan untuk men-disable konfigurasi tertentu.

Merasa kurang cocok dengan solusi tersebut, saya teruskan pencarian alternatif lain sampai saya menemukan artikel milik Josip Lazić [2]. Pada artikel tersebut ternyata dia memiliki masalah yang sama dengan yang saya hadapi, bahkan menemukan link yang sama dengan yang saya temui sebelumnya [1]. Dia pun juga merasa kurang puas dan akhirnya membuat sebuah Shell script sederhana untuk memantain lebih dari satu konfigurasi HAProxy. Pada dasarnya Shell script tersebut menggabungkan file-file konfigurasi berakhiran .cnf di dalam direktori /etc/haproxy/conf.d menjadi sebuah file konfigurasi lengkap /etc/haproxy/haproxy.cfg untuk kemudian me-reload instance HAProxy. Shell script tersebut juga melakukan validasi sintaks konfigurasi sebelum me-reload HAProxy. Saya perlu melakukan beberapa penyesuaian dalam script tersebut, seperti menghilangkan etckeeper dan menggunakan systemd, sehingga saya putuskan untuk mem-fork-nya ke akun Github saya [3]. Shell script ini perlu paket diffcolor untuk proses pembandingan konfigurasi sebelum dan sesudah perubahan.

Berikut adalah struktur dari konfigurasi yang saya lakukan berdasarkan cara kerja Shell script [3]:
Gambar 1. Struktur Multi-Konfigurasi HAProxy

Terlihat pada gambar sebelumnya, semua konfigurasi saya simpan di dalam direktori /etc/haproxy/conf.d. Untuk menghindari salah urutan konfigurasi, saya beri 2 digit nomor sebagai prefix urutannya, sesuai dengan yang disarankan Shell script [3]. Jumlah digit nomor prefiks ini sebetulnya tidak ada keharusan berapa digit, namun sebaiknya 2 digit atau lebih untuk memfasilitasi kemungkinan jumlah project atau aplikasi yang perlu dipisahkan konfigurasinya. Di sini saya mencoba membuat sebuah konvensi penomoran prefiks seperti berikut ini:

  • 0x-nama.cfg - konfigurasi global dan statistik HAProxy
  • 1x-nama.cfg - konfigurasi infrastruktur Nostratech
  • 2x-nama.cfg - konfigurasi project yang sedang berjalan
  • 3x-nama.cfg - konfigurasi demo dan prospek lainnya
  • 9x-nama.cfg - konfigurasi dummy backend

Dengan struktur seperti itu, konfigurasi-konfigurasi yang sifatnya tidak pernah berubah seperti konfigurasi global, errorfile, user, group, dsb, dapat diletakkan pada 00-global.cfg. Kemudian untuk melihat statistik secara instance HAProxy keseluruhan dapat diletakkan di 01-statistics.cfg. Infrastuktur yang sifatnya shareable ke semua project atau prospek seperti MySQL cluster, GlusterFS cluster, dsb. dimasukkan ke dalam konfigurasi 10-nostratech.cfg. Project dan prospek diletakkan di masing-masing konfigurasi, dan dummy backend diletakkan pada 99-dummy.cfg. Ketika misalnya saya perlu menonaktifkan konfigurasi sebuah prospek yang sudah selesai, saya cukup me-rename file tersebut dan menjalankan kembali Shell script-nya.

Konvensi penamaan prefiks yang saya lakukan masih dapat divariasikan kembali tergantung dari kondisi di environment-nya. Sekian artikel saya kali ini. Selamat mencoba! :)

Daftar Referensi:

Tuesday, March 15, 2016

How to Transfer Objects Between UIViewController

Dalam pengembangan aplikasi iOS, seringkali kita membutuhkan untuk mentransfer data dari satu view ke view lainnya. Misalnya pada kasus ketika kita ingin menampilkan informasi detil sebuah user profile hasil dari pencarian user. Bagaimana cara kita melakukan hal tersebut pada iOS? Kesempatan kali ini saya ingin berbagi cara sederhana untuk melakukannya dengan menggunakan bahasa pemrograman Swift dan storyboard.

Pada pengembangan aplikasi iOS menggunakan Xcode, alur dari view aplikasi dapat didisain dengan menggunakan fitur storyboard. Storyboard merupakan fitur yang sangat bermanfaat karena pengembang aplikasi dapat merancang bentuk dan alur dari aplikasi secara independen tidak tergantung dengan kode logika dari aplikasi. Kode logika aplikasi dapat disambungkan dan dilepaskan dengan storyboard dengan usaha yang relatif minimal (cukup Ctrl+Drag). Saya sempat memiliki pengalaman ketika project files saya corrupt sehingga saya harus mendisain ulang storyboard beserta view-nya dari awal. Ketika sudah selesai, storyboard dan kode logika aplikasi dapat disambungkan kembali. Agak banyak, namun setidaknya tidak harus mengkodekan ulang. :)

Gambar di atas menunjukkan storyboard yang saya gunakan pada artikel kali ini. Storyboard aplikasi ini cukup sederhana di mana hanya terdapat 2 view. Pada view pertama hanya terdapat sebuah tombol yang ketika ditekan akan menginisiasi perpindahan view ke view pada sebelah kanan melalui show segue (panah). Perpindahan data di sini hanya akan memindahkan nilai yang dimasukkan pada textfield sehingga label "Nilai" pada view sebelah kanan akan berubah menjadi nilai apapun yang dimasukkan di textfield view pertama. Berilah nama pada show segue tersebut dengan nama yang identik dalam scope 1 project. Pada contoh ini saya beri nama "transferDataSegue". Buat 2 subclass dari UIViewController dan asosiasikan dengan masing-masing view pada storyboard. Saya mengasosiasikan view pertama dengan StartViewController dan view kedua dengan FinishViewController.


Bind textfield di view pertama ke IBOutlet pada StartViewController. Kemudian bind pula label di view kedua ke IBOutlet pada FinishViewController. Buat sebuah variable pada FinishViewController untuk meletakkan nilai yang dipindahkan dari StartViewController. Kunci dari transfer data antarview pada iOS adalah pada fungsi prepareForSegue dari UIViewController. Override fungsi tersebut kemudian isi agar sesuai seperti gambar berikut:

Fungsi prepareForSegue ini akan selalu dipanggil setiap kali terdeteksi adanya transisi dari sebuah view ke view yang lain. Jika segue yang dihubungkan lebih dari 1, maka transisi dapat terjadi beberapa kemungkinan. Oleh karena itu kita membutuhkan identifier unik untuk segue yang dibahas sebelumnya. Pada fungsi ini langkah pertama kita harus menentukan segue mana yang akan dieksekusi, dengan cara memeriksa identifier-nya. Variabel identifier pada object segue berisikan informasi identifier yang sedang dijalankan.

Setelah kita sudah memperoleh object segue yang sedang dieksekusi, kita pastikan terlebih dahulu apakah object segue tersebut memiliki kaitain dengan UIViewController tujuan. UIViewController tujuan memiliki relasi dengan sebuah segue object yaitu pada variabel destinationViewController yang merupakan FinishViewController. Jika sudah tervalidasi, maka proses transfer data antarview cukup dilakukan semudah assignment variabel biasa. Pada FinishViewController, load nilai yang telah diterima dengan meng-assign-nya ke IBOutlet textfield pada fungsi viewDidLoad.

Silahkan coba dijalankan. Sangat mudah bukan? ;)




Friday, February 19, 2016

Using Carthage for iOS Project Dependency Management

Pertama kali saya mulai mengembangkan aplikasi iOS, saya belum mengenal dependency management yang baik. Ketika saya membutuhkan library pihak ketiga dalam project saya, saya menggunakan cara tradisional yaitu download dan include library yang diinginkan ke dalam project utama saya. Cara ini cukup efektif dan segera mudah dimengerti, namun akan mempersulit pengembangan selanjutnya. Setiap ada update dari library yang kita include, kita harus men-download kembali library tersebut. Apabila ada update yang membuat project kita tidak berjalan kita harus rollback kembali dan memilih versi yang tepat.

Menggunakan dependency management untuk iOS project development memiliki beberapa keuntungan dibanding cara tradisional (download dan copy project dari Github). Dependency management dapat mempermudah proses download library yang sesuai dengan yang kita inginkan tanpa harus satu per satu ke halaman Github dari setiap library yang diperlukan, klik download, dan include masing-masing libraray ke dalam project utama. Selain itu kita dapat mengunci versi yang kita perlukan, untuk mencegah kemungkinan code tidak berjalan ketika ada update pada salah satu library-nya.

Ada 2 dependency manager yang biasa digunakan di iOS development, yaitu CocoaPods dan Carthage. Pada kesempatan kali ini saya akan mencoba membahas bagaimana cara menggunakan Carthage, karena saya menggunakan bahasa Swift untuk pengembangan aplikasi. Untuk library yang akan digunakan saya akan menggunakan library untuk mempermudah pemanggilan Web service yaitu Alamofire.

Instalasi Carthage


Ada 2 cara menginstalasi Carthage, yaitu menggunakan Homebrew atau menggunakan package installer. Saya akan menggunakan Homebrew, karena sudah terinstall di MacBook Pro saya. Jalankan 2 perintah berikut di Terminal.
$ brew update
$ brew install carthage

Persiapan Project


Buat project baru pada Xcode, pilih template apa saja. Untuk keperluan artikel ini, saya akan menggunakan template Single View Application.
Xcode New Project Wizard Page 1

Masukkan nama project, dan buat project tersebut.
Xcode New Project Wizard Page 2

Buat sebuah file Cartfile di folder yang sama dengan file .xcodeproj, isi dengan konfigurasi dependency ke library Alamofire seperti berikut ini:
# Depends on Alamofire 3.0
github "Alamofire/Alamofire" ~> 3.0

Simpan file Cartfile, dan lakukan dependency update dengan menjalankan perintah berikut pada folder tersebut dan perhatikan Carthage akan mendownload semua dependency yang diperlukan untuk platform iOS ke dalam direktori Carthage/Checkouts. Selain itu perintah tersebut akan melakukan kompilasi library Alamofire ke dalam direktori Carthage/Build.
$ carthage update --platform iOS
*** Fetching Alamofire
*** Checking out Alamofire at "3.2.0"
*** xcodebuild output can be found in /var/folders/qy/lcpj5ypn1_7_gbfyv5c9dlq40000gn/T/carthage-xcodebuild.119QZ8.log
*** Building scheme "Alamofire iOS" in Alamofire.xcworkspace

Buka kembali project pada Xcode, pilih project root SimpleRestCallDemoApp dan pilih target SimpleRestCallDemoApp. Tambahkan file Alamofire.framework yang telah dikompilasi ke dalam Linked Frameworks and Binaries. Pilih Add Other untuk mengakses langsung dari Finder. Setelah selesai tampilannya akan menjadi seperti ini.
Xcode Linked Frameworks

Untuk memudahkan dalam proses submit aplikasi ke Apple App Store, saran dari [3] adalah dengan menambahkan Run Script pada Build Phases seperti berikut ini.
Xcode Build Phases Run Script

Selesai, kita sekarang siap untuk menggunakan framework Alamofire di dalam project kita.

Referensi

[1] https://github.com/Carthage/Carthage
[2] https://github.com/Alamofire/Alamofire
[3] http://www.raywenderlich.com/109330/carthage-tutorial-getting-started

Tuesday, December 15, 2015

Mengatur UIView dengan Auto Layout pada iOS

Salah satu isu yang saya temui ketika memulai mengembangkan aplikasi mobile, khususnya pada iOS, adalah dalam hal pengaturan layout antarmuka. Seringkali saya sudah meletakkan dengan rapih, namun ketika dijalankan di iOS Simulator layout-nya menjadi berantakan pada device tertentu. Misalnya saya ingin meletakkan 2 label dengan pasangan text field-nya seperti UIView berikut ini:
UIView pada Storyboard

Terlihat pada Storyboard, UIView tersebut sudah rapih. Tetapi setelah dijalankan di iOS Simualtor, hasilnya berantakan tidak sesuai seperti yang saya bayangkan ketika menuangkannya di Storyboard:
UIView ketika runtime di iOS Simulator

Apple telah menyediakan fitur Auto Layout pada iOS untuk mengkalkukasikan secara dinamis jarak dan posisi dari masing-masing komponen antarmuka, sehingga layout dapat tetap rapih dan dinamis pada device apapun. Awalnya saya kurang memahami perilaku dari Auto Layout, sehingga saya merasa menggunakan Auto Layout justru malah mempersulit pengembangan. Ternyata setelah berulang kali saya mencoba, Auto Layout tidak sesulit itu. Bahkan terasa sangat natural dan logis. Pada artikel ini saya ingin berbagi cara memahami dan memanfaatkan Auto Layout dalam pengembangan aplikasi iOS.

Pertama-tama, pastikan "Use Auto Layout" dan "Use Size Classes" pada property Main.storyboard dalam kondisi terpilih. Kemudian ketika kita sedang mendisain UIView di Storyboard, Size Classes berada di posisi "w Any h Any". 
Size Classes Default

Size Classes "w Any h Any" memiliki arti UIView yang sedang diedit akan diaplikasikan secara universal. Artinya kita hanya cukup mendisain layout sekali untuk berbagai ukuran model device maupun orientasi device. Faktor penting lainnya adalah dalam Auto Layout, kita membutukan sebuah object atau batasan untuk menjadi patokan utama. Misalnya pada contoh di atas, hasil akhir yang kita inginkan adalah keseluruhan disain berada tepat di tengah layar bagian atas. Untuk itu kita membutuhkan sebuah object tambahan sebagai patokan titik tengah horizontal. Ambil saja sebuah label baru dan kita letakkan di antara Name dan text field sebelahnya hinggap snap to horizontal centre grid.
Penambahan Komponen Dummy Posisi Tengah Horizontal

Kecilkan ukurannya, kemudian ketika dalam kondisi memilih label tersebut pilih "Pin" di sudut kanan bawah Storyboard dan pilih "Width" dan "Height", kemudian pilih "Align" dan pilih "Horizontally in Container". Tampilannya akan menjadi seperti ini:

Garis merah ini pertanda ada sebuah contraint yang masih tidak benar. Target kita adalah membuat garis merah ini hilang dan menjadi garis biru yang menandakan layout sudah tidak ambigu. Tahan tombol "Ctrl" dan tarik cursor mouse dari label tersebut ke arah atas container dan lepaskan. Kemudian pilih "Vertical Spacing to Top Layout Guide". Seharusnya garis sudah menjadi biru sekarang. 

Kita sudah memiliki patokan titik horizontal sekarang, yaitu label dummy yang telah dibuat sebelumnya.  Sekarang saatnya memanfaatkan titik tersebut. Geser text field ke kanan, label Name ke kiri. Tahan tombol Ctrl dan mouse dari text field ke titik horizontal, begitu pula dengan label Name. Tarik pula dari masing-masing label dan text field ke arah atas ke top container. Jangan lupa untuk membuat fix width pada textfield. Coba jalankan kembali ke iOS Simulator. Seharusnya sekarang posisinya sudah lebih dinamis dan rapih, baik pada oritivertikal maupun horizontal.
Lakukan hal serupa pada label Role dan textfield-nya, kemudian hide titik horizontal. Hasil akhirnya kurang lebih seperti ini:

Selamat mencoba! :)

Monday, December 14, 2015

Cara Mudah Atasi Persoalan Regex

Terkadang saya mendengar keluhan dari rekan-rekan saya ketika mereka menemui permasalahan yang dapat dipermudah jika menggunakan regex. Keluhan-keluhan mereka biasanya merupakan ungkapan bayangan kengerian mereka ketika menggunakan regex. Menurut saya menggunakan regex tidak harus mengerikan, apalagi jika kita menggunakan regex tool yang tepat pengolahan teks menggunakan regex itu dapat menjadi menyenangkan!

Sebelum melanjutkan, saya ingin menegaskan artikel ini tidak akan membahas bagaimana cara menggunakan regex, ataupun membahas satu per satu bagaimana notasi regex pada setiap kasus di masing-masing bahasa pemrograman, karena cukup banyak artikel tutorial regex di Internet yang jauh lebih baik. Namun saya ingin berbagi bagaimana cara saya menyiasati persoalan yang terkait regex.

Metode yang saya lakukan dalam mengolah teks menggunakan pola regex dapat disederhanakan menjadi langkah-langkah berikut:
  1. Copy paste teks sumber yang akan dimanipulasi ke dalam pola regex
  2. Tambahkan tanda awal "^" dan akhiran "$", dan tambahkan modifier single line agar pola bekerja dalam mode single line
  3. Cari bagian-bagian teks utama yang akan diambil atau dijadikan patokan, jepit dengan kurung dan ubah teks utama menjadi notasi regex yang spesifik, misalnya format email, KTP, dsb.
  4. Ubah teks sebelum dan sesudah teks utama menjadi notasi regex yang cukup spesifik untuk menghindari kesalahan pola

Sejak beberapa bulan lalu, saya telah menggunakan online regex tool [1] yang bagus dan sangat membantu saya dalam melakukan pengolahan teks menggunakan regex. Silahkan kunjungi online tool tersebut dan coba untuk memahami masing-masing bagiannya. Artikel ini akan secara ekslusif menggunakan regex101 dalam mendemokan langkah-langkah yang saya gunakan.

Contoh: Mengambil Subset Teks

Contoh sederhana yang sering saya temui adalah bagaimana mengambil subset sebuah teks untuk digunakan di tempat lain. Misalnya ada sebuah teks "token: 12345678, username: okky, role: standard-user", dan kita diminta untuk mengambil nilai token "12345678" saja. Bagaimana pola regex yang tepat untuk digunakan? Mari kita buka regex101 dan masukkan teks ke field Test String dan field Regular Expression seperti gambar berikut ini.

Langkah pertama sudah saya jalankan pada gambar tersebut. Terdengar sederhana, namun dengan kita meng-copy-paste teks sumber ke pola regex awal, kita bisa lebih teliti mensubstitusi masing-masing karakter dengan notasi regex yang sesuai. Suatu ketika saya melewatkan langkah pertama ini, dan saya dihadapi oleh beberapa jam yang terbuang sia-sia, karena saya melewatkan beberapa karakter khusus yang seharusnya saya buatkan notasi regex-nya.

Tambahkan notasi awal dan akhir dan modifer single line. Daftar modifier bisa dilihat dengan cara meng-hover mouse ke tanda tanya di sebelah field modifer. Langkah ini semi opsional, namun pengalaman saya dengan menambahkan modifier ini hasil yang saya peroleh dapat lebih kosisten.

Di sini langkah ketiga jalankan dengan mencari bagian teks utama yaitu "12345678" yang berada persis setelah "token: ". Karena saya tahu bahwa nilai token itu pasti berupa 8 karakter numerik, maka saya substitusikan nilai token menjadi "\d{8}". Jalankan langkah ketiga hasilnya seperti ini:

Setelah mendapatkan informasi dari klien, ternyata urutan variabel belum tentu berurutan token, name, role. Tapi yang pasti pembedanya adalah tanda koma sebelum atau sesudahnya. Dengan demikian pada langkah terakhir kita coba spesifikkan dan sederhanakan pola regex-nya menjadi seperti berikut:

Pola regex akhir yang dihasilkan adalah /^.*,?token:\s*(\d{8}),?.*$/s yang kurang lebih berarti: "Cari kata token: bisa diikuti oleh whitespace (spasi, tab, new line, dsb.) berapapun karakternya, bisa didahului oleh tanda koma, kemudian diikuti oleh 8 karakter numerik dan bisa diakhiri oleh tanda koma". Untuk melihat apakah pola regex yang dihasilkan sukses, bisa lihat dari tanda hijau. Pada gambar berikut terlihat pola yang dihasilkan berhasil menemukan pola setelah 122 langkah. Semakin kecil jumlah langkah semakin efisien pola regex yang kita hasilkan.

Pada bagian kanan tengah terlihat pula matched group yang kita gunakan. Match group ini merupakan hasil dari match group yang kita beri tanda kurung pada pola regex kita dan dapat kita panggil dengan menggunakan "$1" atau "\1" atau group pertama dari object yang matched tergantung dari bahasa pemrograman yang digunakan.

Selama ini 4 langkah yang telah saya jabarkan secara singkat cukup untuk saya menangani berbagai pola regex yang saya temui. Adapun pernah saya menemui kasus yang tidak berhasil saya lakukan dengan langkah tersebut, biasanya saya coba mundur sejenak dan berusaha untuk menyederhanakan persoalan.

Perlu diingat, bahwa tidak selalu pengolahan teks berakhir dengan menggunakan regex. Regex akan lebih tepat jika digunakan untuk mengolah data yang tidak terstruktur seperti teks bebas, untuk pengolahan data yang terstruktur seperti XML, JSON, CSV, dsb., sebaiknya menggunakan parser dari masing-masing format data tersebut. Namun ada kalanya menggunakan parser justru membuat pengolahan teks membutuhkan memory yang besar karena masing-masing bagian akan dikonversi menjadi sebuah object. Biasanya terjadi pada kasus teks terstruktur dengan ukuran yang cukup besar. Salah satu solusinya adalah dengan memecah terlebih dahulu struktur besar tadi menjadi bagian-bagian yang kecil menggunakan regex jika dimungkinkan.

Sekian artikel saya kali ini, Happy Regexing!


Friday, September 18, 2015

Country Selection with Swift in iOS 8


Beberapa bulan terakhir ini, saya mulai mempelajari dan mengembangkan aplikasi iOS menggunakan bahasa Swift. Agar tidak lupa sekaligus menguji pengalaman yang saya peroleh, saya ingin mulai menuangkannya ke topik artikel-artikel saya ke depan. Pada artikel kali ini saya ingin membahas bagaimana cara membuat pilihan negara pada sebuah form menyerupai cara memilih negara pada Address Book iOS. Untuk lebih terbayang tujuan artikel ini, silahkan lihat video berikut ini:

Skenario yang ada pada video tersebut adalah pada scene pertama aplikasi menampilkan scene Edit Profile, di mana salah satu field profile adalah memilih negara. User kemudian memilih negara, lalu transisi ke scene selanjutnya yang muncul dari bawah berupa pilihan negara. Scene pemilihan negara ini tidak hanya berupa daftar negara saja, namun seperti pada Address Book dilengkapi juga dengan indeks huruf depan masing-masing nama negara untuk mempermudah navigasi user.
Gambar 1. Potongan Storyboard

 Storyboard yang saya gunakan pada dasarnya terdiri dari 3 scenes:
  1. EditProfileViewController, subclass dari TableViewController yang saya gunakan untuk scene edit profile.
  2. CountryHelperTableViewController, subclass dari TableViewController yang saya gunakan untuk scene dari pemilihan negara itu sendiri.
  3. NavigationViewController, scene ini bertugas menjembatani dengan EditProfileViewController. NavigationViewController ini berfungsi agar kita dapat melakukan transisi modal dengan lebih baik.
Buat sebuah Swift file bernama CountryHelperTableViewController.swift
import UIKit

class CountryHelperTableViewController: UITableViewController {
    
    var countries: [String]?
    var sectionTitles: [String]?
    var selectedCountryRowId: Int = 0
    var selectedCountryOffset: Int = 0
    var selectedSectionTitleRowId: Int = 0
    var selectedCountry: String?
    var generatedRows: Int = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        populateCountries()
        populateSectionTitles()
        setInitialCountrySelection()
        setSelectedCountryOffset()
    }
    
    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        let indexPath = NSIndexPath(forRow: selectedCountryOffset, inSection: selectedSectionTitleRowId)
        self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Middle, animated: true)
    }
    
    func populateCountries() {
        countries = [String]()
        
        for code in NSLocale.ISOCountryCodes() as! [String] {
            let id = NSLocale.localeIdentifierFromComponents([NSLocaleCountryCode: code])
            let name = NSLocale(localeIdentifier: "en_US").displayNameForKey(NSLocaleIdentifier, value: id) ?? "Country not found for code: \(code)"
            countries!.append(name)
        }
        
        countries!.sort({ $0 < $1 })
    }
    
    func populateSectionTitles() {
        var countrySections = [String: String]()
        
        for country in countries! {
            countrySections[country[0]] = country[0]
        }
        
        var sortedCountrySections = [String]()
        
        for (key, value) in countrySections {
            sortedCountrySections.append(key)
        }
        
        sortedCountrySections.sort({ $0 < $1 })
        sectionTitles = sortedCountrySections
    }
    
    func setInitialCountrySelection() {
        for (index, country) in enumerate(countries!) {
            if country == selectedCountry {
                selectedCountryRowId = index
                
                for (sectionIndex, sectionTitle) in enumerate(sectionTitles!) {
                    if selectedCountry![0] == sectionTitle {
                        selectedSectionTitleRowId = sectionIndex
                        break
                    }
                }
                
                break
            }
        }
    }
    
    func setSelectedCountryOffset() {
        var offset: Int = 0
        
        for country in countries! {
            if country[0] == self.sectionTitles![selectedSectionTitleRowId] {
                if selectedCountry != country {
                    offset++
                } else {
                    break
                }
            }
        }
        
        selectedCountryOffset = offset
    }

    override func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
 if section == tableView.numberOfSections() - 1 {
            return UIView(frame: CGRectMake(0, 0, 1, 1))
 } else {
     return nil
 }
    }

    override func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        if section == tableView.numberOfSections() - 1 {
     return 1
        } else {
            return 0
        }
    }
    
    func rowsInPreviousSection(section: Int) -> Int {
        var rows = 0
        
        for country in self.countries! {
            if self.sectionTitles![section] == country[0] {
                break
            } else {
                rows++
            }
        }
        
        return rows
    }
    
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return self.sectionTitles!.count
    }
    
    override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return self.sectionTitles![section]
    }
    
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let indexTitle = self.sectionTitles![section]
        var numberOfRows = 0

        for country in self.countries! {
            if indexTitle == country[0] {
                numberOfRows++
            }
        }
        
        return numberOfRows
    }
    
    override func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
        return index
    }
    
    override func sectionIndexTitlesForTableView(tableView: UITableView) -> [AnyObject]! {
        return self.sectionTitles
    }
    
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let row = indexPath.row
        let section = indexPath.section
        let prevSectionRows = rowsInPreviousSection(section)
        let cell = self.tableView.dequeueReusableCellWithIdentifier("CountryCell") as! UITableViewCell
        
        cell.textLabel?.text = countries![row + prevSectionRows]
        
        if selectedCountryRowId == row + prevSectionRows {
            cell.accessoryType = UITableViewCellAccessoryType.Checkmark
        } else {
            cell.accessoryType = UITableViewCellAccessoryType.None
        }
        
        return cell
    }
    
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        selectedCountryRowId = indexPath.row + rowsInPreviousSection(indexPath.section)
        tableView.reloadData()
    }
    
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "editProfileUnwindSegueWithValue" {
            if let destinationViewController = segue.destinationViewController as? EditProfileViewController {
                destinationViewController.selectedCountry = countries![selectedCountryRowId]
                destinationViewController.updateSelectedValues()
            }
        }
    }
    
}

Setelah selesai membuat class CountryHelperTableViewController, assign class tersebut ke scene paling kanan. Buat juga String extension seperti berikut ini untuk fungsi mengambil indeks huruf pertama pada nama negara.
import Foundation

extension String {
    
    subscript (i: Int) -> Character {
        return self[advance(self.startIndex, i)]
    }
    
    subscript (i: Int) -> String {
        return String(self[i] as Character)
    }
    
    subscript (r: Range) -> String {
        return substringWithRange(Range(start: advance(startIndex, r.startIndex), end: advance(startIndex, r.endIndex)))
    }
}

Method tableView(_:viewForFooterInSection:) dan tableView(_:heightForFooterInSection:) digunakan agar garis border horizontal dari TableView berakhir pada baris atau nama negara terakhir di dalam array. Perilaku default jika kedua method ini tidak diaplikasikan adalah garis border horizontal akan terus berulang hingga batas layar. Saya lebih suka jika garis horizontal berakhir sesuai dengan data.

Method viewDidLoad() dipanggil pada saat pertama kali saja untuk me-load daftar negara dari library Swift melalui method populateCountries(). Setelah negara-negara ter-load, kita membutuhkan daftar indeks huruf pertama dari masing-masing negara yang ter-load dengan menggunakan method populateSectionTitles(). Method setInitialCountrySelection() digunakan untuk menentukan posisi indeks yang sudah pernah dipilih oleh User dengan nilai default 0 jika User belum pernah melakukan pemilihan negara. Sedangkan method setSelectedCountryOffset() digunakan untuk menentukan offset ke-berapakah negara yang terpilih berdasarkan dari indeks section title. Method ini bermanfaat untuk menentukan TableViewCell manakah yang perlu ditandai dengan checkmark.

Method viewWillAppear() ini pada dasarnya bertujuan untuk melakukan autoscroll terhadap daftar negara, agar fokus dari view berada di tengah-tengah sesuai dengan negara yang pernah dipilih oleh User sebelumnya. Pada method ini tidak perlu lagi me-load daftar negara yang lengkap, karena daftar negara sudah di-load oleh viewDidLoad() untuk pertama kalinya. 

Method tableView(_:numberOfRowsInSection:) merupakan salah satu fungsi penting dalam menampilkan  daftar negara ini. Method inilah yang akan menyaring negara-negara yang sesuai dengan indeks huruf pertamanya. Sementara method tableView(_:sectionForSectionIndexTitle:) dan method sectionIndexTitlesForTableView() berguna untuk menampilkan bar horizontal di bagian kanan yang scene sebagai indeks huruf pertama dari masing-masing negara.

Sekian artikel saya tentang pengembangan iOS kali ini, semoga bermanfaat!

Thursday, September 17, 2015

Active-Active HAProxy Behind Google's Network Load Balancer


Pada artikel saya sebelumnya, saya sempat berbagi bagaimana cara mengkonfigurasi HAProxy secara active-passive menggunakan Keepalived. Namun konfigurasi tersebut tidak dapat diimplementasikan di semua environment. Salah satu isu yang sempat saya temui adalah ketika mencoba mengkonfigurasi hal serupa di Google Cloud Platform (GCP). Akses ke IP public pada GCP tidak dapat dimanipulasi secara langsung dari dalam VM. Masing-masing VM hanya memiliki interface private network, sementara Keepalived membutuhkan akses langsung ke network interface IP public.

GCP menyediakan 2 layanan load balancing pada platform-nya, yaitu HTTP/HTTPS Load Balancing dan Network Load Balancing. Awalnya saya ingin menggunakannya, namun saat artikel ini ditulis status HTTP/HTTPS Load Balancing pada GCP masih dalam beta release. Sehingga saya menjadi enggan untuk menggunakannya di production environment. Di lain pihak status Network Load Balancing pada GCP sudah dalam tahap tidak beta release, tetapi kurang fleksible jika digunakan sebagai load balancer dari Web server. Kerugian lain jika memanfaatkan platform load balancing milik GCP, adalah nantinya semua konfigurasi akan bertumpu pada GCP, sehingga jika suatu saat kita membutuhkan untuk pindah atau ekspansi ke cloud platform perlu konversi kembali pada konfigurasinya.

Akhirnya saya terpikir, bagaimana jika saya kombinasikan antara Network Load Balancer dengan HAProxy servers sebagai backend server-nya? Langsung saja kita lihat diagram berikut ini:
Gambar 1. Diagram Google Network Load Balancer dan HAProxy Server

Ide dasar dari topologi tersebut adalah Google Network Load Balancer hanya difungsikan sebagai IP public provider saja. Semua network traffic yang diterima oleh Google Network Load Balancer akan diteruskan ke HAProxy servers, seolah-olah HAProxy servers berada di DMZ. HAProxy servers yang akan melakukan load balancing yang sebenarnya ke masing-masing backend. Keuntungan yang saya peroleh dengan topologi tersebut antara lain adalah:
  1. Konfigurasi akan dititikberatkan pada HAProxy servers, sehingga tidak perlu melakukan konfigurasi ulang yang banyak jika diperlukan untuk migrasi ke cloud platform lain. 
  2. Fitur-fitur HAProxy seperti connection throttling, IP blacklisting, multiple backend rules dapat dilakukan seperti HAProxy biasa.
  3. HAProxy servers pada topologi tersebut sifatnya active-active, sehingga tidak ada CPU cycle yang terbuang percuma karena idle menunggu master HAProxy down terlebih dahulu.
Sebagai simulasi, saya telah membuat 2 VM bertipe f1-micro yang didedikasikan khusus untuk HAProxy saja.Konfigurasi HAProxy tidak akan saya detilkan pada kesempatan kali ini, karena tidak ada perbedaan yang signifikan. Adapun yang harus dilakukan pada konfigurasi HAProxy adalah tidak menetapkan listen address pada direktif bind seperti 10.11.12.13:80, namun menggunakan karakter asterisk (*) diikuti port, seperti *:80. Hal ini perlu dilakukan agar Google Network Load Balancer dapat berkomunikasi dengan HAProxy.

Langkah-langkah pengerjaan konfigurasi yang perlu dilakukan dapat digambarkan secara sederhana seperti diagram berikut ini:
Gambar 2. Alur Konfigurasi

Pertama-tama external static IP address perlu dibuat terlebih dahulu sebagai pintu masuk semua traffic. Menggunakan external IP address bertipe ephemeral juga dapat dilakukan, namun sebaiknya menggunakan static agar tidak akan berubah-ubah jika ada perubahan konfigurasi. Pastikan untuk tidak meng-attach IP tersebut ke node manapun, karena nantinya external static IP address tersebut akan digunakan oleh forwarding rule.
Gambar 3. Membuat External Static IP Address

Pilih menu Networking -> Network Load Balancing, dan pilih Basic Setup untuk membuat forwarding rule dan target pool yang baru. Isi sesuai dengan environment yang kita miliki. Pada saat menentukan protocol pilih TCP kemudian biarkan Port/range dikosongkan, karena di sini Google Network Load Balancer hanya dimanfaatkan sebagai IP address provider saja dan menyerahkan semua load balancer rule ke HAProxy. Pilih juga untuk membuat Target Pool baru yang anggotanya adalah VM HAProxy. Pada contoh berikut ada 2 instance HAProxy VM yang saya tentukan sebagai Target Pool.
Gambar 4. Forwarding Rule Protocol, Port Range, dan Target Pool

Pada bagian Health Check, buat sebuah health check baru dengan port 9200 dan path /. Untuk sementara kita buat saja seperti ini, nantinya akan kita buat health check khusus untuk HAProxy ini menggunakan xinetd service. Saya buat interval-nya pendek yaitu 1 detik agar jika salah satu instance HAProxy down, request bisa langsung ditangkap oleh HAProxy instance lainnya.
Gambar 5. Health Check HAProxy

Agar health check yang berasal dari Google Network Load Balancer dapat dilakukan, kita perlu meng-update firewall rule untuk memperbolehkan traffic yang berasal dari IP 169.254.169.254, sesuai dengan dokumentasi dari Google [1].
Gambar 6. Firewall Rule untuk Health Check yang dilakukan Google Network Load Balancer

Dari sisi konfigurasi GCP, seharusnya konfigurasi yang sudah dilakukan mencukupi. Namun kita masih perlu membuat health checker untuk HAProxy. Idenya terinspirasi dari script health check yang diperuntukkan untuk memeriksa proses MySQL [2] dengan menggunakan xinetd sebagai service endpoint-nya pada port 9200. Setelah mengikuti langkah-langkah pada artikel tersebut, ternyata Google Network Load Balancer masih menganggap HAProxy pada VM dalam kondisi yang tidak sehat. Setelah saya coba melakukan simulasi operasi download menggunakan wget ke port 9200, dianggap tidak ada. Namun telnet ke 9200 sudah mengembalikan output yang saya harapkan. Sepertinya Google Network Load Balancer mengharapkan health checker-nya berupa HTTP response dengan HTTP Status 200, sedangkan dengan mekanisme xinetd murni tidak seperti yang diharapkan.

Kemudian saya coba mencari cara lain dan menemui cara untuk mensimulasikan response HTTP menggunakan script Python [3]. Script xinetd tetap digunakan sebagai health check service endpoint, namun xinetd script tersebut memanggil Python script yang dapat mensimulasikan sebuah HTTP server sederhana yang akan mengembalikan HTTP Status 200 jika proses HAProxy pada VM tersebut masih berjalan, dan akan mengembalikan HTTP Status 503 jika tidak ada proses HAProxy yang berjalan.

Berikut adalah Python script yang digunakan:
#!/usr/bin/python

import subprocess
import sys

while True:
    data = sys.stdin.readline().strip()
    if data == "":
        subprocess.call(['/opt/haproxycheck.sh'])
        f = open('/tmp/haproxycheck.out','r')
        if f.read() != "":
            print 'HTTP/1.1 200 OK'
            print 'Content-Type: Content-Type: text/plain'
            print ''
            print 'HAProxy is running.'
            print ''
        else:
            print 'HTTP/1.1 503 Service Unavailable'
            print 'Content-Type: Content-Type: text/plain'
            print ''
            print 'HAProxy is down.'
            print ''
        sys.stdout.flush()
        break


Script tersebut memanggil Shell script untuk memeriksa proses HAProxy, di mana output dari Shell script tersebut dapat menghasilkan 2 output file yaitu /tmp/haproxycheck.out jika HAProxy berjalan normal dan /tmp/haproxycheck.err jika HAProxy tidak berjalan. Berikut adalah Shell script yang dimaksud:
#!/bin/bash

TMP_FILE="/tmp/haproxycheck.out"
ERR_FILE="/tmp/haproxycheck.err"

/bin/pidof haproxy > $TMP_FILE 2> $ERR_FILE


Restart xinetd service dan seharusnya dalam beberapa saat status health check dari HAProxy sudah dalam kondisi sehat seperti ini:
Gambar 7. Status Health Check HAProxy
Solusi yang saya aplikasikan di sini masih sangat mungkin untuk dioptimalkan kembali, terutama script Python dan Shell untuk meminimalkan overhead yang terjadi. Namun setidaknya dari artikel yang saya tulis ada gambaran bagaimana mengaplikasikan health check pada environment Google Cloud platform. Silahkan mencoba!


Daftar Referensi:
[1] Load Balancing Health Checks - Update Firewall Rules
[2] Having HAProxy check mysql status through a xinetd script, Unai Rodriguez, 4 December 2008
[3] Writing a Python xinetd Server, Johan Jordaan, November 2011