[{"content":"","date":"5 Maret 2026","externalUrl":null,"permalink":"/tags/autoscaling/","section":"Tags","summary":"","title":"Autoscaling","type":"tags"},{"content":"","date":"5 Maret 2026","externalUrl":null,"permalink":"/authors/hasbi/","section":"Penulis","summary":"","title":"Hasbi","type":"authors"},{"content":"Dokumentasi sekaligus panduan seputar cloud dan layanan server.\n","date":"5 Maret 2026","externalUrl":null,"permalink":"/","section":"Jagoan Devops","summary":"Dokumentasi sekaligus panduan seputar cloud dan layanan server.\n","title":"Jagoan Devops","type":"page"},{"content":"","date":"5 Maret 2026","externalUrl":null,"permalink":"/categories/","section":"Kategori","summary":"","title":"Kategori","type":"categories"},{"content":"","date":"5 Maret 2026","externalUrl":null,"permalink":"/authors/","section":"Penulis","summary":"","title":"Penulis","type":"authors"},{"content":"","date":"5 Maret 2026","externalUrl":null,"permalink":"/tags/queue/","section":"Tags","summary":"","title":"Queue","type":"tags"},{"content":"","date":"5 Maret 2026","externalUrl":null,"permalink":"/posts/","section":"Semua Artikel","summary":"","title":"Semua Artikel","type":"posts"},{"content":"","date":"5 Maret 2026","externalUrl":null,"permalink":"/categories/server/","section":"Kategori","summary":"","title":"Server","type":"categories"},{"content":"","date":"5 Maret 2026","externalUrl":null,"permalink":"/tags/superscaler/","section":"Tags","summary":"","title":"Superscaler","type":"tags"},{"content":"Superscaler adalah service untuk melakukan autoscaling terhadap worker Supervisor. Service ini dirancang untuk menambah atau mengurangi jumlah proses worker di Supervisor secara otomatis sesuai dengan beban kerja yang masuk.\nSupervisor secara bawaan tidak mendukung fitur autoscaling. Namun, Supervisor menyebutkan di dokumentasinya bahwa mereka menyediakan RPC interface yang fungsionalitasnya dapat diperluas.\nSupervisor\u0026#39;s XML-RPC interface may be extended arbitrarily by programmers. Additional top-level namespace XML-RPC interfaces can be added using the [rpcinterface:foo] declaration in the configuration file. Superscaler memanfaatkan celah ini untuk memanipulasi in-memory process dictionary milik Supervisor melalui custom plugin. Hasilnya, Superscaler bisa menambah atau mengurangi jumlah worker tanpa perlu me-restart worker lain yang sedang memproses queue. Selain itu, Superscaler juga mendukung beberapa queue backend secara bersamaan. Setiap target bisa memonitor sistem queue yang berbeda. Backend yang didukung saat ini adalah RabbitMQ (via AMQP) dan Redis (via list length).\nSuperscaler terdiri dari dua komponen:\nMain Daemon (superscaler) Supervisor RPC Plugin (superscaler_plugin) Algoritma Scaling # Untuk setiap target yang dikonfigurasi, daemon Superscaler secara berkala melakukan pengecekan berdasarkan konfigurasi poll_interval.\nSuperscaler mengambil queue depth dari backend yang dikonfigurasi dan menghitung: desired_workers = ceil(queue_len / tasks_per_worker). Membatasi desired_workers di antara min_workers dan max_workers. Mengecek jumlah worker aktif yang sedang berjalan di Supervisor. Jika active \u0026lt; desired: mengirim RPC call scaleUp (maksimal sebanyak scale_up_step) dengan syarat waktu cooldown_up sudah terlewati dan tidak ada proses yang sedang dalam tahap di-stop. Jika active \u0026gt; desired: mengirim RPC call scaleDown (maksimal sebanyak scale_down_step) dengan syarat waktu cooldown_down sudah terlewati dan tidak ada proses yang sedang dalam tahap di-stop. Cara Kerja # Supervisor standar tidak mendukung penambahan atau penghapusan proses secara dinamis tanpa melakukan reload yang bersifat disruptif. Maka dari itu, Superscaler menyediakan custom XML-RPC plugin ([rpcinterface:superscaler]) untuk mengatasi keterbatasan tersebut.\nScaling Up # Plugin secara dinamis menaikkan nilai numprocs di dalam file konfigurasi .ini pada disk, kemudian melakukan re-parse secara internal menggunakan parser bawaan Supervisor, dan langsung membuat objek worker baru ke dalam live supervisor memory dictionary. Secara spesifik, RPC plugin membandingkan konfigurasi group yang baru di-parse dengan in-memory process directory yang sedang berjalan. Untuk setiap nama proses baru yang ditemukan (misalnya worker_03), plugin membuat objek Process internal menggunakan make_process() milik Supervisor, menambahkannya ke dict group.processes, dan mengandalkan transisi main loop Supervisor berikutnya untuk secara natural (auto_spawn) memulai proses tersebut ke state STARTING.\nScaling Down # Untuk menghindari kill paksa terhadap job yang sedang berjalan, scaleDown hanya mengirim sinyal graceful stop ke proses dengan nomor tertinggi terlebih dahulu. Daemon secara berkala mengecek state dari worker yang sedang di-stop melalui polling. Hanya ketika state mereka berhasil bertransisi ke STOPPED_STATES, daemon baru mengirim confirmScaleDown. Pada fase konfirmasi ini, plugin menulis ulang file konfigurasi pada disk untuk secara resmi menurunkan numprocs, melakukan re-parse, dan akhirnya menghapus instance Process yang sudah di-stop dari dict group.processes. Urutan operasi yang presisi ini mencegah terjadinya divergensi fatal antara in-memory state dan file konfigurasi jika sistem crash di tengah proses.\nClustering # Superscaler mendukung arsitektur clustering master/slave untuk mengelola worker Supervisor di beberapa server sekaligus.\nMaster node menjalankan daemon Superscaler (superscaler). Master membaca konfigurasi, melakukan polling terhadap queue, dan mengirimkan perintah scaling ke semua node yang dikonfigurasi. Sedangkan slave node hanya menjalankan Supervisor dengan superscaler_plugin terpasang. Slave mengekspos HTTP XML-RPC endpoint supaya master bisa menjangkaunya secara remote. Slave node tidak perlu menjalankan daemon Superscaler.\nUntuk node lokal (di mesin yang sama dengan daemon), Superscaler berkomunikasi melalui Unix socket. Untuk node remote, Superscaler menggunakan HTTP XML-RPC standar (xmlrpc.client.ServerProxy) dengan opsional HTTP Basic Auth.\nKonfigurasi Node # Setiap node didefinisikan dengan section [node:\u0026lt;name\u0026gt;] di dalam superscaler.conf. Nama yang digunakan adalah identifier unik yang bisa dipilih sendiri (misalnya local, worker-1).\nParameter Deskripsi url Wajib. Endpoint URL. Gunakan http://host:port/RPC2 untuk remote node atau unix:///path/to/socket untuk local node. username Username Supervisor untuk autentikasi. Kosongkan jika tidak ada. password Password Supervisor untuk autentikasi. Kosongkan jika tidak ada. Contoh konfigurasi node:\n[node:local] url = unix:///var/run/supervisor.sock username = password = [node:worker-1] url = http://192.168.1.10:9001/RPC2 username = admin password = secret [node:worker-2] url = http://192.168.1.11:9001/RPC2 username = admin password = secret Parameter nodes pada Target # Setiap section [target:*] menerima parameter nodes berupa daftar nama [node:\u0026lt;name\u0026gt;] yang dipisahkan koma. Parameter ini menentukan node mana saja yang akan di-scale oleh target tersebut.\n[target:main-scaler] type = supervisor queue = main-rabbit queue_key = tasks program_name = example-worker nodes = local, worker-1, worker-2 tasks_per_worker = 50 min_workers = 2 max_workers = 30 Ketika scaling up, worker ditambahkan ke node yang memiliki worker aktif paling sedikit (least-loaded). Ketika scaling down, worker dihapus dari node yang memiliki worker aktif paling banyak (most-loaded).\nJika tidak ada section [node:*] yang didefinisikan tapi section [supervisor] ada, Superscaler secara otomatis membuat default node dari konfigurasi [supervisor]. Konfigurasi lama tetap berjalan tanpa perubahan.\nPrasyarat # Komponen Versi Minimum Python 3.9 redis-py 4.0.0 pika 1.2.0 Superscaler menyediakan package instalasi standar untuk distribusi .rpm dan .deb.\nInstalasi # Red Hat / CentOS # Download package RPM curl -LO https://github.com/mijonOfTheMoon/superscaler/releases/download/3.0.0/superscaler-3.0.0-1.amzn2023.noarch.rpm Instal package sudo dnf install superscaler-3.0.0-1.amzn2023.noarch.rpm Debian / Ubuntu # Download package DEB curl -LO https://github.com/mijonOfTheMoon/superscaler/releases/download/3.0.0/superscaler_3.0.0-1_all.deb Instal package sudo dpkg -i superscaler_3.0.0-1_all.deb Service Superscaler tidak diaktifkan atau dijalankan secara otomatis setelah instalasi. Hal ini disengaja karena slave node hanya membutuhkan plugin, bukan daemon.\nSetup Master Node # Master node menjalankan daemon Superscaler dan mengkoordinasikan scaling ke semua node.\nInstal package (RPM atau DEB seperti di atas).\nKonfigurasi /etc/superscaler/superscaler.conf dengan section [node:\u0026lt;name\u0026gt;] untuk setiap node Supervisor di dalam cluster:\n[node:local] url = unix:///var/run/supervisor.sock username = password = [node:worker-1] url = http://192.168.1.10:9001/RPC2 username = admin password = secret Definisikan target dengan parameter nodes yang merujuk ke node yang sudah dikonfigurasi: [target:main-scaler] type = supervisor queue = main-rabbit queue_key = tasks program_name = example-worker nodes = local, worker-1 tasks_per_worker = 50 min_workers = 2 max_workers = 20 Aktifkan dan jalankan service Superscaler: sudo systemctl enable superscaler sudo systemctl start superscaler Jika master node juga menjalankan worker Supervisor, tambahkan plugin ke supervisord.conf lokal: [rpcinterface:superscaler] supervisor.rpcinterface_factory = superscaler_plugin.rpcinterface:SuperscalerNamespaceRPCInterface Kemudian restart Supervisor:\nsudo systemctl restart supervisor Setup Slave Node # Slave node menjalankan Supervisor dengan plugin dan mengekspos HTTP XML-RPC endpoint. Daemon Superscaler tidak perlu berjalan di slave node.\nInstal package (RPM atau DEB seperti di atas). Service tetap disabled secara default.\nTambahkan plugin [rpcinterface:superscaler] ke /etc/supervisor/supervisord.conf:\n[rpcinterface:superscaler] supervisor.rpcinterface_factory = superscaler_plugin.rpcinterface:SuperscalerNamespaceRPCInterface Aktifkan section [inet_http_server] di /etc/supervisor/supervisord.conf supaya master bisa menjangkau node ini melalui HTTP: [inet_http_server] port = 0.0.0.0:9001 username = admin password = secret Gunakan password yang kuat dan batasi akses jaringan (firewall rules, private network) untuk mengamankan endpoint.\nRestart Supervisor untuk mengapply perubahan: sudo systemctl restart supervisor Cukup itu saja untuk slave. Tidak perlu mengaktifkan atau menjalankan service Superscaler karena semua keputusan scaling ditangani oleh master node secara remote.\nKonfigurasi # Tambahkan plugin berikut ke dalam konfigurasi supervisord.conf:\n[rpcinterface:superscaler] supervisor.rpcinterface_factory = superscaler_plugin.rpcinterface:SuperscalerNamespaceRPCInterface Setelah menambahkan plugin, konfigurasi Superscaler bisa dilakukan. Path default untuk file konfigurasi Superscaler adalah /etc/superscaler/superscaler.conf.\nSection [supervisor] # Mengkonfigurasi communication layer ke daemon Supervisor.\nParameter Deskripsi unix_socket_path UNIX socket URI untuk XML-RPC (contoh: unix:///var/run/supervisor.sock) username Username Supervisor. Kosongkan jika tidak ada. password Password Supervisor. Kosongkan jika tidak ada. Section [queue:\u0026lt;name\u0026gt;] # Mendefinisikan queue backend. Beberapa backend dapat dikonfigurasi secara bersamaan. Parameter type menentukan backend driver yang digunakan.\nParameter Deskripsi type Wajib. Tipe backend: rabbitmq atau redis Parameter RabbitMQ (type = rabbitmq):\nParameter Deskripsi host Hostname server RabbitMQ (contoh: 127.0.0.1) port Port AMQP (contoh: 5672) username Username RabbitMQ (contoh: guest) password Password RabbitMQ (contoh: guest) vhost Virtual host (contoh: /) Parameter Redis (type = redis):\nParameter Deskripsi host IP atau hostname server Redis (contoh: 127.0.0.1) port Port Redis (contoh: 6379) password Password Redis. Kosongkan jika tidak ada. db Index integer Redis DB (contoh: 0) Section [target:\u0026lt;nama_target\u0026gt;] # Setiap target worker pool harus didefinisikan dengan prefix [target:\u0026lt;nama_target\u0026gt;]. Misalnya, [target:example-scaler].\nParameter Deskripsi queue Wajib. Nama section [queue:*] yang digunakan sebagai queue backend. queue_key Wajib. Key atau nama queue yang dimonitor di backend. program_name Wajib. Nama program Supervisor yang akan di-autoscale. tasks_per_worker Wajib. Rasio pending tasks yang diharapkan untuk setiap worker. min_workers Wajib. Batas minimum jumlah proses worker. max_workers Wajib. Batas maksimum jumlah proses worker. poll_interval Opsional. Durasi dalam detik antar pengecekan queue. Default 10. scale_up_step Opsional. Batas jumlah worker yang ditambahkan per aksi scale up. Default 1. scale_down_step Opsional. Batas jumlah worker yang dihapus per aksi scale down. Default 1. cooldown_up Opsional. Durasi aman dalam detik sebelum mengizinkan scale up berikutnya. Default 0. cooldown_down Opsional. Durasi aman dalam detik sebelum mengizinkan scale down berikutnya. Default 0. nodes Opsional. Daftar nama [node:\u0026lt;name\u0026gt;] yang dipisahkan koma untuk multi-node scaling. Post Konfigurasi # Untuk setup single-node menggunakan section [supervisor], restart kedua service:\nsudo systemctl restart supervisor sudo systemctl restart superscaler Untuk setup multi-node cluster, ikuti panduan Setup Master Node dan Setup Slave Node di atas.\n","date":"5 Maret 2026","externalUrl":null,"permalink":"/posts/superscaler/","section":"Semua Artikel","summary":"Superscaler adalah service untuk melakukan autoscaling terhadap worker Supervisor. Service ini dirancang untuk menambah atau mengurangi jumlah proses worker di Supervisor secara otomatis sesuai dengan beban kerja yang masuk.\n","title":"Superscaler, Autoscaler Open-Source untuk Supervisor Workers","type":"posts"},{"content":"","date":"5 Maret 2026","externalUrl":null,"permalink":"/tags/supervisor/","section":"Tags","summary":"","title":"Supervisor","type":"tags"},{"content":"","date":"5 Maret 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"6 Januari 2026","externalUrl":null,"permalink":"/tags/aws/","section":"Tags","summary":"","title":"AWS","type":"tags"},{"content":"","date":"6 Januari 2026","externalUrl":null,"permalink":"/tags/ci/cd/","section":"Tags","summary":"","title":"CI/CD","type":"tags"},{"content":"","date":"6 Januari 2026","externalUrl":null,"permalink":"/categories/cloud/","section":"Kategori","summary":"","title":"Cloud","type":"categories"},{"content":"","date":"6 Januari 2026","externalUrl":null,"permalink":"/tags/codebuild/","section":"Tags","summary":"","title":"CodeBuild","type":"tags"},{"content":"","date":"6 Januari 2026","externalUrl":null,"permalink":"/tags/container/","section":"Tags","summary":"","title":"Container","type":"tags"},{"content":"","date":"6 Januari 2026","externalUrl":null,"permalink":"/tags/ecr/","section":"Tags","summary":"","title":"ECR","type":"tags"},{"content":"","date":"6 Januari 2026","externalUrl":null,"permalink":"/tags/ecs/","section":"Tags","summary":"","title":"ECS","type":"tags"},{"content":"AWS menyediakan banyak layanan untuk membangun ekosistem containerized application di cloud. Opsi deployment disediakan dari yang paling mudah di App Runner, hingga yang paling kompleks di EKS. Bahkan, untuk CI/CD saja, disediakan CodePipeline, CodeBuild, CodeDeploy, dan CodeCatalyst. Tapi pada kenyataannya, tidak semua project butuh orkestrasi yang kompleks. Kadang, ECS ditambah dengan CodeBuild buildspec.yaml custom saja sudah lebih dari cukup untuk menangani seluruh lifecycle containermu.\nArtikel ini mendokumentasikan studi kasus ekosistem pengembangan aplikasi berbasis AI, Aibeecara Indonesia, dengan komponen AI wrapper Django, backend Django, dan mobile apps Flutter. Dua aplikasi pertama dideploy ke Amazon ECS dengan launch type EC2, kemudian mobile apps diupload ke Google Drive menggunakan gdrive CLI.\nArsitektur # Sebelum mulai, mari kita pahami bersama diagram rancangan infrastruktur Aibeecara Indonesia.\nSebagai catatan, perlu diingat bahwa produk Aibeecara saat ini sudah memasuki tahap production. Estimasi pengguna awal adalah 10-15 pengguna, dengan traffic yang sangat sporadis.\nSecara garis besar, ekosistem container yang akan dibangun terdiri dari komponen-komponen berikut:\nCodeBuild, untuk menjalankan CI/CD ECR, untuk menyimpan container image ECS, untuk menjalankan container Secrets Manager, untuk menyimpan secrets Sebagai wadah deployment, disini kami memilih menggunakan ECS dengan launch type EC2.\nKok, gak pake fargate aja?\nYes, sekali lagi ingat bahwa kondisi produk Aibeecara saat ini adalah production skala kecil. Kami butuh solusi yang minim expense, namun juga butuh service dengan reliablity tinggi.\nECS sendiri tidak memungut biaya untuk control planenya. Yang dibayar hanyalah compute resource yang menjalankan container. Pada launch type EC2, compute resource tersebut adalah instance EC2 yang dikelola sendiri. Fargate memang lebih simpel, AWS yang mengelola computenya, tapi dengan harga yang lebih mahal.\nSebagai gambaran, instance EC2 t4g.small dengan 2vCPU dan 2GB RAM dibanderol seharga $15.48 per bulan di ap-southeast-3. Apalagi jika dikombinasikan dengan Reserved Instances atau Savings Plans, penghematan bisa mencapai 40 sampai 60% dibandingkan Fargate.1\nYa, memang ada spot fargate dengan harga yang mirip dengan EC2 on-demand. Namun tetap saja, secara naturalnya, spot instance tidak bisa menjanjikan SLA, sehingga kurang cocok untuk use case ini.\nAlur Deployment # Untuk Backend dan AI # Kedua aplikasi ini memiliki alur CI/CD yang identik, hanya berbeda di nama service, repository ECR, dan task definitionnya.\nDeveloper melakukan push ke branch di repository Webhook CodeBuild tertrigger CodeBuild menjalankan buildspec.yaml: Phase pre_build: Login ke ECR dan menyiapkan variabel image tag Phase build: Membuild Docker image dan mentagnya Phase post_build: Mempush image ke ECR, mendaftarkan task definition baru, kemudian mengupdate ECS service ECS melakukan rolling update, mengganti task lama dengan task baru yang menggunakan image terbaru Proses rolling update ini bersifat zero-downtime. ECS akan menjalankan task baru terlebih dahulu, memastikan task tersebut healthy, baru kemudian menghentikan task lama. Dengan begitu, aplikasi tetap bisa diakses selama proses deployment berlangsung.\nUntuk Mobile Apps # Developer melakukan push ke branch di repository Webhook CodeBuild tertrigger CodeBuild menjalankan buildspec.yaml: Phase install: Menyiapkan Java, Android SDK, Flutter SDK, dan gdrive CLI, kemudian mengimport akun Google Drive Phase pre_build: Meresolve branch ke folder Drive tujuan, mengambil versi dari pubspec.yaml, mendecode keystore, menyiapkan key.properties, dan menjalankan flutter pub get Phase build: Membuild APK untuk semua branch, dan AAB untuk branch selain develop Phase post_build: Mengenerate changelog dari git log, mengupload folder release ke Google Drive sesuai branch, kemudian menghapus file signing sensitif Pertama, Yuk Persiapkan IAM # Agar seluruh ekosistem ini berjalan, ada tiga IAM role yang perlu dikonfigurasi, yaitu task execution role ECS, service role CodeBuild, dan instance role ECS.\nECS Task Execution Role # Task execution role adalah role yang digunakan oleh ECS agent untuk menarik image dari ECR dan mengambil secrets dari Secrets Manager pada saat task dijalankan. Role ini berbeda dengan task role yang digunakan oleh aplikasi di dalam container.\nBuka menu IAM, pilih Roles di menu sebelah kiri, dan pilih Create Role\nPada step 1, Trusted entity type, pilih AWS service. Kemudian, untuk use case, pilih Elastic Container Service Task. Kemudian klik next untuk ke step selanjutnya.\nDi step 2, cari dan pilih AmazonECSTaskExecutionRolePolicy, kemudian klik next.\nTerakhir, beri nama dan deskripsi, lalu klik Create role di bagian bawah\nSetelah berhasil membuat role baru, pergi ke halaman detail role dan add permission. Pilih yang inline policy\nMasukkan policy untuk ECS task execution role berikut.\n{ \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [ { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;ecr:GetAuthorizationToken\u0026#34;, \u0026#34;ecr:BatchCheckLayerAvailability\u0026#34;, \u0026#34;ecr:GetDownloadUrlForLayer\u0026#34;, \u0026#34;ecr:BatchGetImage\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;logs:CreateLogGroup\u0026#34;, \u0026#34;logs:CreateLogStream\u0026#34;, \u0026#34;logs:PutLogEvents\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: \u0026#34;secretsmanager:GetSecretValue\u0026#34;, \u0026#34;Resource\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:*\u0026#34; } ] } Beri nama untuk policy baru ini, kemudian klik create policy.\nAWS sebenarnya sudah menyediakan managed policy AmazonECSTaskExecutionRolePolicy yang mencakup permission ECR dan CloudWatch Logs. Tapi karena policy tersebut tidak mencakup Secrets Manager, kita perlu menambahkan permission secretsmanager:GetSecretValue secara terpisah.\nCodeBuild Service Role # CodeBuild service role membutuhkan permission untuk berinteraksi dengan ECR, ECS, dan Secrets Manager.\nPilih Create role pada menu IAM sekali lagi. Kali ini, pilih CodeBuild sebagai use casenya.\nSkip step ke-2, kita tidak membutuhkan AWS managed policy pada role ini. Klik next untuk lanjut ke step 3 dan memberikan nama beserta deskripsi. Kemudian, scroll ke bawah untuk menekan tombol create role.\nSetelah role berhasil dibuat, tambahkan policy CodeBuild berikut ini. Tambahkan melalui inline policy, seperti yang sebelumnya telah dilakukan pada ECS task execution role\n{ \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [ { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;ecr:GetAuthorizationToken\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;ecr:BatchCheckLayerAvailability\u0026#34;, \u0026#34;ecr:GetDownloadUrlForLayer\u0026#34;, \u0026#34;ecr:BatchGetImage\u0026#34;, \u0026#34;ecr:PutImage\u0026#34;, \u0026#34;ecr:InitiateLayerUpload\u0026#34;, \u0026#34;ecr:UploadLayerPart\u0026#34;, \u0026#34;ecr:CompleteLayerUpload\u0026#34; ], \u0026#34;Resource\u0026#34;: [ \u0026#34;arn:aws:ecr:ap-southeast-3:xxxxxxxxxxxx:repository/backend\u0026#34;, \u0026#34;arn:aws:ecr:ap-southeast-3:xxxxxxxxxxxx:repository/ai\u0026#34; ] }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;ecs:DescribeTaskDefinition\u0026#34;, \u0026#34;ecs:RegisterTaskDefinition\u0026#34;, \u0026#34;ecs:UpdateService\u0026#34;, \u0026#34;ecs:DescribeServices\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: \u0026#34;iam:PassRole\u0026#34;, \u0026#34;Resource\u0026#34;: [ \u0026#34;arn:aws:iam::xxxxxxxxxxxx:role/aibeecaraECSTaskExecutionRole\u0026#34;, \u0026#34;arn:aws:iam::xxxxxxxxxxxx:role/ecsTaskRole\u0026#34; ] }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: \u0026#34;secretsmanager:GetSecretValue\u0026#34;, \u0026#34;Resource\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;logs:CreateLogGroup\u0026#34;, \u0026#34;logs:CreateLogStream\u0026#34;, \u0026#34;logs:PutLogEvents\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; } ] } Kemudian simpan. Perhatikan bahwa ecr:GetAuthorizationToken membutuhkan Resource * karena action ini memang tidak mendukung resource-level permission. Berbeda dengan action ECR lainnya yang discope ke repository spesifik supaya CodeBuild hanya bisa mempush ke repository tersebut. Remember to always scope your policy properly, bro.\nECS Instance Role # ECS Instance role digunakan oleh ECS instance untuk mendaftarkan diri ke cluster. Role ini harus memiliki AWS managed policy yaitu AmazonEC2ContainerServiceforEC2Role.\nPilih Create role pada menu IAM sekali lagi. Kali ini, pilih EC2 sebagai use casenya. Klik next untuk lanjut ke step selanjutnya.\nPada step 2, cari dan pilih AmazonEC2ContainerServiceforEC2Role. Centang dan klik next untuk pergi ke tahap review\nBeri nama, lalu create role\nSelesai! Ketiga role ini akan diattach pada tahapan selanjutnya.\nKonfigurasi Secrets Manager # Secrets Manager digunakan untuk menyimpan semua kredensial dan konfigurasi sensitif yang dibutuhkan oleh aplikasi maupun proses CI/CD. Setiap secret disimpan dalam format JSON key-value pair.\nSecrets Manager memungut biaya sebesar $0.40 per *secret* per bulan dan $0.05 per 10.000 API call.2 Maka dari itu, sebaiknya gabungkan beberapa key-value pair yang terkait ke dalam satu secret untuk menghemat biaya.\nUntuk menambahkan secret baru, pergi ke menu Secrets Manager dan pilih Store a new secret.\nUntuk secret type, pilih yang other type of secret.\nUntuk ekosistem Aibeecara, secret dikelompokkan berdasarkan aplikasi sebagai berikut.\nPertama, secret untuk AI wrapper, berisi kredensial ke layanan AI eksternal.\n{ \u0026#34;GEMINI_KEY\u0026#34;: \u0026#34;AIzaSyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u0026#34;, \u0026#34;DEEPGRAM_KEY\u0026#34;: \u0026#34;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u0026#34;, \u0026#34;EVALUATION_BASE_URL\u0026#34;: \u0026#34;https://evaluation.internal.aibeecara/api\u0026#34; } Kedua, secret untuk backend, berisi seluruh environment variable sensitif termasuk koneksi database, Redis, JWT, Midtrans, dan SMTP.\n{ \u0026#34;SECRET_KEY\u0026#34;: \u0026#34;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u0026#34;, \u0026#34;JWT_SECRET_KEY\u0026#34;: \u0026#34;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u0026#34;, \u0026#34;DB_NAME\u0026#34;: \u0026#34;aibeecara_prod\u0026#34;, \u0026#34;DB_USER\u0026#34;: \u0026#34;aibeecara_user\u0026#34;, \u0026#34;DB_PASSWORD\u0026#34;: \u0026#34;xxxxxxxxxxxxxxxxxxxxxxxx\u0026#34;, \u0026#34;DB_HOST\u0026#34;: \u0026#34;db.internal.aibeecara\u0026#34;, \u0026#34;DB_PORT\u0026#34;: \u0026#34;5432\u0026#34;, \u0026#34;REDIS_URL\u0026#34;: \u0026#34;redis://redis.internal.aibeecara:6379/0\u0026#34;, \u0026#34;MIDTRANS_SERVER_KEY\u0026#34;: \u0026#34;Mid-server-xxxxxxxxxxxxxxxxxxxxxxxx\u0026#34;, \u0026#34;MIDTRANS_CLIENT_KEY\u0026#34;: \u0026#34;Mid-client-xxxxxxxxxxxxxxxxxxxxxxxx\u0026#34;, \u0026#34;EMAIL_HOST_USER\u0026#34;: \u0026#34;notif@aibeecara.click\u0026#34;, \u0026#34;EMAIL_HOST_PASSWORD\u0026#34;: \u0026#34;xxxxxxxxxxxxxxxx\u0026#34; } Ketiga, secret untuk mobile signing, berisi keystore dan kredensial signing.\n{ \u0026#34;KEYSTORE_BASE64\u0026#34;: \u0026#34;MIIKXQIBAzCCCh...base64encodedkeystore...\u0026#34;, \u0026#34;KEYSTORE_PASSWORD\u0026#34;: \u0026#34;xxxxxxxxxxxxxxxx\u0026#34;, \u0026#34;KEY_ALIAS\u0026#34;: \u0026#34;aibeecara\u0026#34;, \u0026#34;KEY_PASSWORD\u0026#34;: \u0026#34;xxxxxxxxxxxxxxxx\u0026#34; } Keempat, secret untuk gdrive account archive, disimpan sebagai plaintext secret dalam format base64. Archive ini berasal dari perintah gdrive account export yang dijalankan di localhost. Karena kita belum sampai disana, berikan string dummy terlebih dahulu.\nanGzb2FAAAAAAA= Kelima, secret untuk Github personal access token yang akan digunakan sebagai autentikasi CodeBuild ke GitHub saat mendaftarkan webhook. Secret ini juga Disimpan sebagai plaintext secret. Proses pembuatan Github personal access token dapat merujuk ke dokumentasi resmi. Scope yang dibutuhkan untuk personal access tokennya adalah admin:repo_hook dan repo.\nBerdasarkan (dokumentasi resmi)[https://docs.aws.amazon.com/codebuild/latest/userguide/asm-create-secret.html], format penyimpanan kredensial Github untuk Codebuild adalah sebagai berikut.\n{ \u0026#34;ServerType\u0026#34;: \u0026#34;GITHUB\u0026#34;, \u0026#34;AuthType\u0026#34;: \u0026#34;PERSONAL_ACCESS_TOKEN\u0026#34;, \u0026#34;Token\u0026#34;: \u0026#34;ghp_8jYxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0Gy\u0026#34; } Setelah memasukkan JSONnya, beri nama untuk secret tersebut dan klik next hingga step terakhir, kemudian store.\nUlangi prosesnya hingga keempat secret untuk masing-masing aplikasi berhasil dibuat.\nDengan metode seperti ini, total secret yang digunakan hanya ada 5, yaitu seharga $2.00 per bulan. Jika masing masing variable di*treat* sebagai satu secret, maka nantinya akan ada puluhan *secret* yang totalnya bisa tembus lebih dari $10 per bulan.\nRetrieve Secrets di ECS Task Definition, Gimana sih? # ECS dapat menginjeksi secrets langsung ke dalam container melalui task definition. Dengan cara ini, environment variable sensitif tidak perlu dihardcode di mana pun. ECS akan mengambil nilainya dari Secrets Manager pada saat task dijalankan.\n{ \u0026#34;containerDefinitions\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;backend\u0026#34;, \u0026#34;image\u0026#34;: \u0026#34;xxxxxxxxxxxx.dkr.ecr.ap-southeast-3.amazonaws.com/backend:latest\u0026#34;, \u0026#34;secrets\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;SECRET_KEY\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/backend/prod-xxxxxx:SECRET_KEY::\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;DB_PASSWORD\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/backend/prod-xxxxxx:DB_PASSWORD::\u0026#34; } ] } ] } Format ARN untuk mengambil key spesifik dari sebuah secret adalah sebagai berikut.\narn:aws:secretsmanager:\u0026lt;region\u0026gt;:\u0026lt;account_id\u0026gt;:secret:\u0026lt;secret_name\u0026gt;-\u0026lt;6_random_chars\u0026gt;:\u0026lt;json_key\u0026gt;:: Perhatikan 6 karakter acak di belakang nama secret. Karakter ini otomatis digenerate Secrets Manager saat secret dibuat dan wajib dicantumkan di ARN. Jika ARN ditulis tanpa karakter ini, ECS akan gagal start task dengan error ResourceInitializationError: unable to pull secrets.3\nDua titik dua di akhir ARN adalah version-stage dan version-id yang dikosongkan, sehingga ECS akan selalu mengambil versi terbaru dari secret tersebut.\nPerlu diingat juga, execution role dari task definition harus memiliki permission secretsmanager:GetSecretValue terhadap ARN secret yang dirujuk. Tanpa permission ini, task akan gagal saat startup karena tidak bisa mengambil secret.\nKalo Retrieve Secrets di CodeBuild, Gimana? # CodeBuild juga bisa mengambil secrets dari Secrets Manager dan menjadikannya environment variable selama proses build. Konfigurasinya bisa dilakukan di dua tempat, yaitu di level project CodeBuild atau langsung di buildspec.yaml.\nPada konfigurasi environment variable CodeBuild project, pilih tipe Secrets Manager dan masukkan referensi secret beserta keynya.\nName Value Type DB_PASSWORD aibeecara/backend/prod:DB_PASSWORD Secrets Manager MIDTRANS_SERVER_KEY aibeecara/backend/prod:MIDTRANS_SERVER_KEY Secrets Manager Atau, secrets juga bisa dideklarasikan langsung di buildspec.yaml menggunakan section secrets-manager.\nenv: secrets-manager: DB_PASSWORD: aibeecara/backend/prod:DB_PASSWORD MIDTRANS_SERVER_KEY: aibeecara/backend/prod:MIDTRANS_SERVER_KEY Kedua cara ini menghasilkan hal yang sama, yaitu environment variable yang bisa diakses di dalam buildspec.yaml seperti environment variable biasa. Perbedaannya adalah, konfigurasi di level project tidak akan terekspos di source code. Sedangkan, konfigurasi di buildspec.yaml akan lebih mudah untuk ditrack perubahannya melalui version control.\nKonfigurasi ECR # Amazon Elastic Container Registry, biasa disingkat ECR, digunakan sebagai private registry untuk menyimpan container image. Setiap aplikasi memiliki repository ECRnya masing-masing.\nECR memungut biaya sebesar $0.10 per GB per bulan untuk penyimpanan image di private repository.4 Biaya ini tergolong murah, tapi bisa membengkak jika image lama tidak pernah dibersihkan. Untuk menghemat storage, aktifkan lifecycle policy yang secara otomatis menghapus image lama.\nBuat repository ECR untuk setiap aplikasi.\naws ecr create-repository --repository-name backend --region ap-southeast-3 aws ecr create-repository --repository-name ai --region ap-southeast-3 Cek menu Elastic Container Registry pada AWS console dan perhatikan bahwa kedua repository telah berhasil dibuat.\nLifecycle Policy # Tanpa lifecycle policy, setiap kali CodeBuild mempush image baru, image lama tetap tersimpan di ECR. Seiring waktu, storage yang terpakai akan terus bertambah. Lifecycle policy mengatasi masalah ini dengan menghapus image secara otomatis berdasarkan kriteria tertentu.\nBuat file bernama lifecycle-policy.json dan masukkan lifecycle policy berikut supaya repository hanya menyimpan 10 image terakhir.\n{ \u0026#34;rules\u0026#34;: [ { \u0026#34;rulePriority\u0026#34;: 1, \u0026#34;description\u0026#34;: \u0026#34;Simpan hanya 10 image terakhir\u0026#34;, \u0026#34;selection\u0026#34;: { \u0026#34;tagStatus\u0026#34;: \u0026#34;any\u0026#34;, \u0026#34;countType\u0026#34;: \u0026#34;imageCountMoreThan\u0026#34;, \u0026#34;countNumber\u0026#34;: 10 }, \u0026#34;action\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;expire\u0026#34; } } ] } Terapkan lifecycle policy ke repository.\naws ecr put-lifecycle-policy \\ --repository-name backend \\ --lifecycle-policy-text file://lifecycle-policy.json \\ --region ap-southeast-3 Perlu diperhatikan bahwa lifecycle policy dievaluasi dalam waktu 24 jam setelah diterapkan, bukan langsung saat itu juga.5 Jadi jangan panik kalau image lama belum langsung terhapus.\nUntuk repository ai, jalankan command yang sama dengan mengganti parameter \u0026ndash;repository-name menjadi ai.\nKonfigurasi ECS # Konfigurasi ECS pada study case ini terdiri dari empat bagian. Pertama, ECS cluster sebagai environment untuk menjalankan task dan service. Kedua, ECS Instance, yaitu EC2 tempat container akan berjalan nantinya. Ketiga, task definition untuk backend dan AI wrapper yang mendefinisikan bagaimana masing-masing container akan dijalankan. Keempat, ECS service yang membuat task tersebut berjalan di instance yang terdaftar di cluster.\nECS Cluster # Langkah pertama adalah membuat ECS cluster dengan nama dan region yang sesuai. Aibeecara memiliki user base di Indonesia, sehingga lokasi yang cocok untuk production adalah di Jakarta.\naws ecs create-cluster \\ --cluster-name production \\ --region ap-southeast-3 ECS Instance # ECS instance adalah EC2 instance yang menjalankan ECS Agent dan terdaftar ke cluster. nstance ini harus menggunakan ECS-optimized AMI versi ARM karena instance yang digunakan adalah t4g.small yang berbasis Graviton.\nSebelum membuat instance, siapkan beberapa variabel yang dibutuhkan terlebih dahulu. Pertama, siapkan AMI ID yang akan digunakan.\nAMI_ID=$(aws ssm get-parameters \\ --names /aws/service/ecs/optimized-ami/amazon-linux-2/arm64/recommended \\ --query \u0026#39;Parameters[0].Value\u0026#39; \\ --output text \\ --region ap-southeast-3 | jq -r \u0026#39;.image_id\u0026#39;) Kemudian, persiapkan subnet id yang akan digunakan.\nSUBNET_ID=$(aws ec2 describe-subnets \\ --filters Name=default-for-az,Values=true \\ --query \u0026#39;Subnets[0].SubnetId\u0026#39; \\ --output text \\ --region ap-southeast-3) Jika terjadi error karena tidak ada default VPC, jalankan aws ec2 create-default-vpc --region ap-southeast-3 terlebih dahulu, lalu ulangi command di atas.\nKemudian, buat key pair untuk akses SSH ke instance.\naws ec2 create-key-pair \\ --key-name aibeecara-key \\ --query \u0026#39;KeyMaterial\u0026#39; \\ --output text \\ --region ap-southeast-3 \u0026gt; aibeecara-key.pem chmod 400 aibeecara-key.pem Buat security group dan buka port yang dibutuhkan. Port 8000 untuk backend dan port 22 untuk SSH. Port 50051 tidak perlu dibuka karena AI wrapper hanya dikomunikasikan secara internal via localhost dari backend.\nSECURITY_GROUP_ID=$(aws ec2 create-security-group \\ --group-name aibeecara-sg \\ --description \u0026#34;Security group for aibeecara ECS instance\u0026#34; \\ --query \u0026#39;GroupId\u0026#39; \\ --output text \\ --region ap-southeast-3) aws ec2 authorize-security-group-ingress \\ --group-id $SECURITY_GROUP_ID \\ --protocol tcp --port 8000 --cidr 0.0.0.0/0 \\ --region ap-southeast-3 aws ec2 authorize-security-group-ingress \\ --group-id $SECURITY_GROUP_ID \\ --protocol tcp --port 22 --cidr 0.0.0.0/0 \\ --region ap-southeast-3 Setelah semua variabel siap, launch EC2 instance dengan menyisipkan User Data. Tujuannya adalah, supaya ECS Agent otomatis mendaftarkan instance ke cluster production saat pertama kali boot.\naws ec2 run-instances \\ --image-id $AMI_ID \\ --instance-type t4g.small \\ --key-name aibeecara-key \\ --security-group-ids $SECURITY_GROUP_ID \\ --subnet-id $SUBNET_ID \\ --iam-instance-profile Name=aibeecaraECSInstanceRole \\ --user-data \u0026#39;#!/bin/bash echo ECS_CLUSTER=production \u0026gt;\u0026gt; /etc/ecs/ecs.config\u0026#39; \\ --region ap-southeast-3 Verifikasi melalui AWS console bahwa instance tersebut sudah masuk ke dalam container instances milik cluster production\nECS Task Definition # Berikut contoh task definition untuk AI wrapper. Aplikasi ini nantinya akan listen di port 50051 untuk gRPC dan dipanggil oleh backend via localhost.\nBuat file bernama ai-task.json dengan isi sebagai berikut:\n{ \u0026#34;family\u0026#34;: \u0026#34;ai-task\u0026#34;, \u0026#34;networkMode\u0026#34;: \u0026#34;host\u0026#34;, \u0026#34;requiresCompatibilities\u0026#34;: [\u0026#34;EC2\u0026#34;], \u0026#34;executionRoleArn\u0026#34;: \u0026#34;arn:aws:iam::xxxxxxxxxxxx:role/aibeecaraECSTaskExecutionRole\u0026#34;, \u0026#34;cpu\u0026#34;: \u0026#34;1024\u0026#34;, \u0026#34;memory\u0026#34;: \u0026#34;1024\u0026#34;, \u0026#34;containerDefinitions\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;ai\u0026#34;, \u0026#34;image\u0026#34;: \u0026#34;xxxxxxxxxxxx.dkr.ecr.ap-southeast-3.amazonaws.com/ai:latest\u0026#34;, \u0026#34;essential\u0026#34;: true, \u0026#34;secrets\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;GEMINI_KEY\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/ai/prod-xxxxxx:GEMINI_KEY::\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;DEEPGRAM_KEY\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/ai/prod-xxxxxx:DEEPGRAM_KEY::\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;EVALUATION_BASE_URL\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/ai/prod-xxxxxx:EVALUATION_BASE_URL::\u0026#34; } ], \u0026#34;logConfiguration\u0026#34;: { \u0026#34;logDriver\u0026#34;: \u0026#34;awslogs\u0026#34;, \u0026#34;options\u0026#34;: { \u0026#34;awslogs-group\u0026#34;: \u0026#34;/ecs/ai\u0026#34;, \u0026#34;awslogs-region\u0026#34;: \u0026#34;ap-southeast-3\u0026#34;, \u0026#34;awslogs-stream-prefix\u0026#34;: \u0026#34;ecs\u0026#34;, \u0026#34;awslogs-create-group\u0026#34;: \u0026#34;true\u0026#34; } } } ] } Task definition untuk backend polanya sama, hanya saja image menunjuk ke repository backend, bagian environment dan secrets disesuaikan, dan AI_GRPC_TARGET diset ke localhost:50051.\nBuat juga backend-task.json dengan isi sebagai berikut:\n{ \u0026#34;family\u0026#34;: \u0026#34;backend-task\u0026#34;, \u0026#34;networkMode\u0026#34;: \u0026#34;host\u0026#34;, \u0026#34;requiresCompatibilities\u0026#34;: [\u0026#34;EC2\u0026#34;], \u0026#34;executionRoleArn\u0026#34;: \u0026#34;arn:aws:iam::xxxxxxxxxxxx:role/aibeecaraECSTaskExecutionRole\u0026#34;, \u0026#34;cpu\u0026#34;: \u0026#34;1024\u0026#34;, \u0026#34;memory\u0026#34;: \u0026#34;1024\u0026#34;, \u0026#34;containerDefinitions\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;backend\u0026#34;, \u0026#34;image\u0026#34;: \u0026#34;xxxxxxxxxxxx.dkr.ecr.ap-southeast-3.amazonaws.com/backend:latest\u0026#34;, \u0026#34;essential\u0026#34;: true, \u0026#34;environment\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;ENV\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;production\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;DEBUG\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;False\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;ALLOWED_HOSTS\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;AI_GRPC_TARGET\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;localhost:50051\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;AI_GRPC_SECURE\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;False\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;EMAIL_HOST\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;smtp.gmail.com\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;EMAIL_PORT\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;587\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;EMAIL_USE_TLS\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;True\u0026#34; } ], \u0026#34;secrets\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;SECRET_KEY\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/backend/prod-xxxxxx:SECRET_KEY::\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;JWT_SECRET_KEY\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/backend/prod-xxxxxx:JWT_SECRET_KEY::\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;DB_NAME\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/backend/prod-xxxxxx:DB_NAME::\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;DB_USER\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/backend/prod-xxxxxx:DB_USER::\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;DB_PASSWORD\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/backend/prod-xxxxxx:DB_PASSWORD::\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;DB_HOST\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/backend/prod-xxxxxx:DB_HOST::\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;DB_PORT\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/backend/prod-xxxxxx:DB_PORT::\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;REDIS_URL\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/backend/prod-xxxxxx:REDIS_URL::\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;MIDTRANS_SERVER_KEY\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/backend/prod-xxxxxx:MIDTRANS_SERVER_KEY::\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;MIDTRANS_CLIENT_KEY\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/backend/prod-xxxxxx:MIDTRANS_CLIENT_KEY::\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;EMAIL_HOST_USER\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/backend/prod-xxxxxx:EMAIL_HOST_USER::\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;EMAIL_HOST_PASSWORD\u0026#34;, \u0026#34;valueFrom\u0026#34;: \u0026#34;arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/backend/prod-xxxxxx:EMAIL_HOST_PASSWORD::\u0026#34; } ], \u0026#34;logConfiguration\u0026#34;: { \u0026#34;logDriver\u0026#34;: \u0026#34;awslogs\u0026#34;, \u0026#34;options\u0026#34;: { \u0026#34;awslogs-group\u0026#34;: \u0026#34;/ecs/backend\u0026#34;, \u0026#34;awslogs-region\u0026#34;: \u0026#34;ap-southeast-3\u0026#34;, \u0026#34;awslogs-stream-prefix\u0026#34;: \u0026#34;ecs\u0026#34;, \u0026#34;awslogs-create-group\u0026#34;: \u0026#34;true\u0026#34; } } } ] } Variabel yang tidak bersifat rahasia seperti ENV, DEBUG, AI_GRPC_TARGET, dan EMAIL_HOST ditaruh di environment saja. Tidak perlu ditaruh di secrets supaya tidak terhitung sebagai secret baru di Secrets Manager.\nRegisterkan kedua file task definition yang telah dibuat menggunakan command berikut.\naws ecs register-task-definition \\ --cli-input-json file://ai-task.json \\ --region ap-southeast-3 aws ecs register-task-definition \\ --cli-input-json file://backend-task.json \\ --region ap-southeast-3 Dapat diverifikasi menggunakan console bahwa kedua task definition sudah berhasil dibuat\nECS Service # ECS service untuk kedua aplikasi dibuat dengan schedulingStrategy: DAEMON. Dengan strategy ini, desired count tidak perlu diset manual, ECS otomatis menjalankan satu task di setiap container instance di cluster.6\naws ecs create-service \\ --cluster production \\ --service-name ai-service \\ --task-definition ai-task \\ --launch-type EC2 \\ --scheduling-strategy DAEMON \\ --region ap-southeast-3 Service untuk backend dibuat dengan pola yang sama.\naws ecs create-service \\ --cluster production \\ --service-name backend-service \\ --task-definition backend-task \\ --launch-type EC2 \\ --scheduling-strategy DAEMON \\ --region ap-southeast-3 Setiap instance akan dapat satu backend dan satu AI wrapper. Nantinya, scaling akan dilakukan di level EC2 Auto Scaling Group. Untuk melakukan scaling, cukup tambah instance untuk menambah kapasitas backend dan AI wrapper sekaligus.\nKonfigurasi CodeBuild # CodeBuild adalah fully managed build service yang menjalankan proses CI/CD berdasarkan instruksi di buildspec.yaml. CodeBuild memberikan 100 menit build gratis per bulan untuk tipe general1.small atau arm1.small.7 Free tier ini tersedia untuk pelanggan baru maupun lama, dan tidak ikut kedaluwarsa bersama free tier 12 bulan. Untuk kebanyakan proses build Docker image yang sederhana, general1.small sudah lebih dari cukup.\nIntegrasi GitHub # Sebelum membuat project CodeBuild, CodeBuild perlu mendapatkan izin untuk mengakses repository Github. Caranya adalah dengan mendaftarkan personal access token dari Github ke CodeBuild.\nPersonal access token yang dibutuhkan harus memiliki scope repo dan admin:repo_hook. Pastikan sudah tersimpan ke dalam Secrets Manager seperti yang sudah dilakukan di bagian sebelumnya. Kemudian, jalankan command berikut.\naws codebuild import-source-credentials \\ --server-type GITHUB \\ --auth-type SECRETS_MANAGER \\ --token arn:aws:secretsmanager:ap-southeast-3:xxxxxxxxxxxx:secret:aibeecara/repo/prod \\ --region ap-southeast-3 Codebuild Build Projects # Satu hal yang sering terlewat saat pertama kali setup CodeBuild untuk Docker build adalah privileged mode. Flag ini wajib diaktifkan supaya Docker daemon bisa berjalan di dalam container build. Tanpa privileged mode, semua perintah docker build akan gagal dengan pesan Cannot connect to the Docker daemon.8\nAktifkan privileged mode saat membuat project via CLI dengan parameter privilegedMode=true di dalam environment.\naws codebuild create-project \\ --name backend-cicd \\ --source \u0026#34;type=GITHUB,location=https://github.com/Aibeecara-Development/aibeecara_be.git\u0026#34; \\ --artifacts \u0026#34;type=NO_ARTIFACTS\u0026#34; \\ --environment \u0026#34;type=LINUX_CONTAINER,image=aws/codebuild/amazonlinux-x86_64-standard:5.0,computeType=BUILD_GENERAL1_SMALL,privilegedMode=true\u0026#34; \\ --service-role arn:aws:iam::xxxxxxxxxxxx:role/aibeecaraCloudBuildRole \\ --region ap-southeast-3 Lakukan hal yang sama untuk project AI wrapper. Karena juga membuild Docker image, privileged mode tetap wajib diaktifkan.\naws codebuild create-project \\ --name ai-cicd \\ --source \u0026#34;type=GITHUB,location=https://github.com/Aibeecara-Development/ai-aibeecara.git\u0026#34; \\ --artifacts \u0026#34;type=NO_ARTIFACTS\u0026#34; \\ --environment \u0026#34;type=LINUX_CONTAINER,image=aws/codebuild/amazonlinux-x86_64-standard:5.0,computeType=BUILD_GENERAL1_SMALL,privilegedMode=true\u0026#34; \\ --service-role arn:aws:iam::xxxxxxxxxxxx:role/aibeecaraCloudBuildRole \\ --region ap-southeast-3 Untuk project mobile, privileged mode tidak diperlukan karena tidak membuild Docker image.\naws codebuild create-project \\ --name mobile-cicd \\ --source \u0026#34;type=GITHUB,location=https://github.com/Aibeecara-Development/aibeecara_mobile.git\u0026#34; \\ --artifacts \u0026#34;type=NO_ARTIFACTS\u0026#34; \\ --environment \u0026#34;type=LINUX_CONTAINER,image=aws/codebuild/amazonlinux-x86_64-standard:5.0,computeType=BUILD_GENERAL1_SMALL\u0026#34; \\ --service-role arn:aws:iam::xxxxxxxxxxxx:role/aibeecaraCloudBuildRole \\ --region ap-southeast-3 Ketiga build projects tersebut dapat divalidasi melalui AWS console, pada menu Developer Tools \u0026gt; CodeBuild \u0026gt; Build projects\nWebhook Trigger # CodeBuild dapat menerima webhook dari GitHub, GitHub Enterprise Server, GitLab, GitLab Self Managed, dan Bitbucket.9 Dengan webhook, setiap push ke branch yang ditentukan akan otomatis mentrigger proses build tanpa perlu menjalankannya secara manual.\nBuat webhook untuk project backend dan AI menggunakan command berikut.\naws codebuild create-webhook \\ --project-name backend-cicd \\ --filter-groups \u0026#39;[[{\u0026#34;type\u0026#34;:\u0026#34;EVENT\u0026#34;,\u0026#34;pattern\u0026#34;:\u0026#34;PUSH\u0026#34;},{\u0026#34;type\u0026#34;:\u0026#34;HEAD_REF\u0026#34;,\u0026#34;pattern\u0026#34;:\u0026#34;^refs/heads/main$\u0026#34;}]]\u0026#39; \\ --region ap-southeast-3 aws codebuild create-webhook \\ --project-name ai-cicd \\ --filter-groups \u0026#39;[[{\u0026#34;type\u0026#34;:\u0026#34;EVENT\u0026#34;,\u0026#34;pattern\u0026#34;:\u0026#34;PUSH\u0026#34;},{\u0026#34;type\u0026#34;:\u0026#34;HEAD_REF\u0026#34;,\u0026#34;pattern\u0026#34;:\u0026#34;^refs/heads/main$\u0026#34;}]]\u0026#39; \\ --region ap-southeast-3 aws codebuild create-webhook \\ --project-name mobile-cicd \\ --filter-groups \u0026#39;[[{\u0026#34;type\u0026#34;:\u0026#34;EVENT\u0026#34;,\u0026#34;pattern\u0026#34;:\u0026#34;PUSH\u0026#34;},{\u0026#34;type\u0026#34;:\u0026#34;HEAD_REF\u0026#34;,\u0026#34;pattern\u0026#34;:\u0026#34;^refs/heads/main$\u0026#34;}]]\u0026#39; \\ --region ap-southeast-3 Pada konfigurasi di atas, filter group dibuat supaya hanya push ke branch main yang mentrigger build, supaya push ke branch feature tidak memakan jatah build minutes.\nSetelah berhasil dibuat, hasil konfigurasi webhook dapat dilihat melalui AWS console, pada menu Developer Tools \u0026gt; CodeBuild \u0026gt; Build projects \u0026gt; nama build projects\nBuildspec Backend dan AI # Berikut adalah contoh buildspec.yaml untuk aplikasi backend. Konfigurasi untuk AI wrapper pada dasarnya sama, hanya berbeda di nama service, repository, dan task definition.\nversion: 0.2 env: variables: AWS_ACCOUNT_ID: \u0026#34;xxxxxxxxxxxx\u0026#34; AWS_DEFAULT_REGION: \u0026#34;ap-southeast-3\u0026#34; IMAGE_REPO_NAME: \u0026#34;backend\u0026#34; ECS_CLUSTER: \u0026#34;production\u0026#34; ECS_SERVICE: \u0026#34;backend-service\u0026#34; TASK_FAMILY: \u0026#34;backend-task\u0026#34; CONTAINER_NAME: \u0026#34;backend\u0026#34; phases: pre_build: commands: - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com - REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7) - IMAGE_TAG=${COMMIT_HASH:=latest} build: commands: - docker build -t $REPOSITORY_URI:latest . - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG post_build: commands: - docker push $REPOSITORY_URI:latest - docker push $REPOSITORY_URI:$IMAGE_TAG - TASK_DEFINITION=$(aws ecs describe-task-definition --task-definition \u0026#34;$TASK_FAMILY\u0026#34; --region \u0026#34;$AWS_DEFAULT_REGION\u0026#34;) - NEW_TASK_DEF=$(echo $TASK_DEFINITION | jq --arg IMAGE \u0026#34;$REPOSITORY_URI:$IMAGE_TAG\u0026#34; \u0026#39;.taskDefinition | .containerDefinitions[0].image = $IMAGE | del(.taskDefinitionArn) | del(.revision) | del(.status) | del(.requiresAttributes) | del(.compatibilities) | del(.registeredAt) | del(.registeredBy) | del(.deregisteredAt) | del(.enableFaultInjection)\u0026#39;) - NEW_TASK_INFO=$(aws ecs register-task-definition --region \u0026#34;$AWS_DEFAULT_REGION\u0026#34; --cli-input-json \u0026#34;$NEW_TASK_DEF\u0026#34;) - NEW_REVISION=$(echo $NEW_TASK_INFO | jq -r \u0026#39;.taskDefinition.taskDefinitionArn\u0026#39;) - aws ecs update-service --cluster $ECS_CLUSTER --service $ECS_SERVICE --task-definition $NEW_REVISION --region $AWS_DEFAULT_REGION Tambahkan buildspec.yaml di root project repositori Aibeecara supaya dapat dibaca oleh Codebuild. Variabel xxxxxxxxxxxx disini hanya sebagai contoh placeholder, demi alasan keamanan.\nBagian yang paling penting adalah proses update task definition di fase post_build. Karena task definition bersifat immutable, setiap deployment harus membuat revision baru.10 Proses ini dilakukan dengan mengambil task definition yang sedang aktif, mengganti image URInya, menghapus field yang tidak diperlukan untuk registrasi ulang, kemudian mendaftarkannya sebagai revision baru*. Setelah itu, update-service dengan revision terbaru memaksa ECS untuk melakukan rolling update.\nBuildspec Mobile Apps # Berbeda dengan backend yang cukup butuh Docker, mobile apps butuh toolchain yang lebih panjang yaitu Java JDK, Android SDK, Flutter SDK, plus gdrive CLI untuk upload hasil build. Image CodeBuild default tidak menyediakan Flutter dan Android SDK, jadi keduanya perlu diinstall di fase install.\ngdrive CLI dari Glotlabs yang dipakai di sini adalah command-line client untuk Google Drive yang mendukung operasi upload, download, list, dan delete.11 Untuk penggunaan di environment CI/CD yang headless, akun gdrive perlu disetup terlebih dahulu di localhost, kemudian diexport dan diimport ke environment CodeBuild.\nLangkah persiapan di localhost:\nBuat Google OAuth Client credentials di Google Cloud Console Download binary gdrive dari halaman release Tambahkan akun Google ke gdrive. Selengkapnya di sini gdrive account add Export akun yang sudah ditambahkan gdrive account export nama_akun@gmail.com Simpan file archive hasil export ke Secrets Manager sebagai plaintext secret. Archive diencode ke base64 terlebih dahulu supaya aman saat diinject sebagai environment variable di CodeBuild. Bagi pengguna Linux, gunakan command ini:\nbase64 -w 0 gdrive_export-nama_akun_gmail_com.tar Bagi pengguna Windows PowerShell, gunakan command ini:\n[Convert]::ToBase64String([IO.File]::ReadAllBytes(\u0026#34;$PWD\\gdrive_export-nama_akun_gmail_com.tar\u0026#34;)) Update di secret manager. Berikut adalah contoh buildspec.yaml untuk mobile apps Flutter, diport dari workflow GitHub Actions existing. Buildspec ini mendukung tiga branch, yaitu production, staging, dan develop, masing-masing mengarah ke folder Google Drive yang berbeda.\nversion: 0.2 env: variables: FLUTTER_VERSION: \u0026#34;3.35.0\u0026#34; JAVA_VERSION: \u0026#34;21\u0026#34; GDRIVE_VERSION: \u0026#34;3.9.1\u0026#34; secrets-manager: GDRIVE_ACCOUNT_ARCHIVE: aibeecara/mobile/gdrive-account KEYSTORE_BASE64: aibeecara/mobile/signing:KEYSTORE_BASE64 KEYSTORE_PASSWORD: aibeecara/mobile/signing:KEYSTORE_PASSWORD KEY_ALIAS: aibeecara/mobile/signing:KEY_ALIAS KEY_PASSWORD: aibeecara/mobile/signing:KEY_PASSWORD DRIVE_PRODUCTION: aibeecara/mobile/drive:DRIVE_PRODUCTION DRIVE_STAGING: aibeecara/mobile/drive:DRIVE_STAGING DRIVE_PREVIEW: aibeecara/mobile/drive:DRIVE_PREVIEW phases: install: runtime-versions: java: corretto21 commands: - mkdir -p /opt/android-sdk/cmdline-tools - curl -LO https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip - unzip -q commandlinetools-linux-11076708_latest.zip -d /opt/android-sdk/cmdline-tools - mv /opt/android-sdk/cmdline-tools/cmdline-tools /opt/android-sdk/cmdline-tools/latest - export ANDROID_HOME=/opt/android-sdk - export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH - yes | sdkmanager --licenses \u0026gt; /dev/null - sdkmanager \u0026#34;platform-tools\u0026#34; \u0026#34;platforms;android-34\u0026#34; \u0026#34;build-tools;34.0.0\u0026#34; \u0026gt; /dev/null - git clone https://github.com/flutter/flutter.git -b stable --depth 1 /opt/flutter - cd /opt/flutter \u0026amp;\u0026amp; git checkout $FLUTTER_VERSION - export PATH=/opt/flutter/bin:$PATH - flutter --disable-analytics - flutter precache --android - curl -LO https://github.com/glotlabs/gdrive/releases/download/$GDRIVE_VERSION/gdrive_linux-x64.tar.gz - tar -xzf gdrive_linux-x64.tar.gz - chmod +x gdrive - mv gdrive /usr/local/bin/ - echo \u0026#34;$GDRIVE_ACCOUNT_ARCHIVE\u0026#34; | base64 -d \u0026gt; /tmp/gdrive-account.tar - gdrive account import /tmp/gdrive-account.tar - rm /tmp/gdrive-account.tar pre_build: commands: - BRANCH_NAME=$(echo \u0026#34;$CODEBUILD_WEBHOOK_HEAD_REF\u0026#34; | sed \u0026#39;s|refs/heads/||\u0026#39;) - | if [ \u0026#34;$BRANCH_NAME\u0026#34; = \u0026#34;develop\u0026#34; ]; then FOLDER_PREFIX=\u0026#34;frontend\u0026#34; DRIVE_PARENT=\u0026#34;$DRIVE_PREVIEW\u0026#34; elif [ \u0026#34;$BRANCH_NAME\u0026#34; = \u0026#34;production\u0026#34; ]; then FOLDER_PREFIX=\u0026#34;aibeecara\u0026#34; DRIVE_PARENT=\u0026#34;$DRIVE_PRODUCTION\u0026#34; else FOLDER_PREFIX=\u0026#34;aibeecara\u0026#34; DRIVE_PARENT=\u0026#34;$DRIVE_STAGING\u0026#34; fi - VERSION=$(grep \u0026#39;^version: \u0026#39; pubspec.yaml | cut -d \u0026#39; \u0026#39; -f 2 | tr -d \u0026#39;\\r\u0026#39;) - VERSION=\u0026#34;$VERSION-build-$CODEBUILD_BUILD_NUMBER\u0026#34; - FOLDER_NAME=\u0026#34;$FOLDER_PREFIX v$VERSION\u0026#34; - mkdir \u0026#34;$FOLDER_NAME\u0026#34; - sed -i \u0026#39;s/minSdk *= *23/minSdk = flutter.minSdkVersion/\u0026#39; android/app/build.gradle.kts - echo \u0026#34;$KEYSTORE_BASE64\u0026#34; | base64 -d \u0026gt; android/app/aibeecara.jks - echo \u0026#34;storePassword=$KEYSTORE_PASSWORD\u0026#34; \u0026gt; android/key.properties - echo \u0026#34;keyPassword=$KEY_PASSWORD\u0026#34; \u0026gt;\u0026gt; android/key.properties - echo \u0026#34;keyAlias=$KEY_ALIAS\u0026#34; \u0026gt;\u0026gt; android/key.properties - echo \u0026#34;storeFile=aibeecara.jks\u0026#34; \u0026gt;\u0026gt; android/key.properties - flutter pub get build: commands: - flutter build apk --release - mv build/app/outputs/flutter-apk/app-release.apk \u0026#34;$FOLDER_NAME/aibeecara.apk\u0026#34; - | if [ \u0026#34;$BRANCH_NAME\u0026#34; != \u0026#34;develop\u0026#34; ]; then flutter build appbundle --release mv build/app/outputs/bundle/release/app-release.aab \u0026#34;$FOLDER_NAME/aibeecara.aab\u0026#34; fi post_build: commands: - | if [ -n \u0026#34;$CODEBUILD_WEBHOOK_PREV_COMMIT\u0026#34; ]; then COMMIT_RANGE=\u0026#34;$CODEBUILD_WEBHOOK_PREV_COMMIT..$CODEBUILD_RESOLVED_SOURCE_VERSION\u0026#34; else COMMIT_RANGE=\u0026#34;$CODEBUILD_RESOLVED_SOURCE_VERSION~10..$CODEBUILD_RESOLVED_SOURCE_VERSION\u0026#34; fi - git log --pretty=format:\u0026#34;- %s (%an)\u0026#34; --no-merges $COMMIT_RANGE \u0026gt; \u0026#34;$FOLDER_NAME/changelog.md\u0026#34; || echo \u0026#34;- No new commits\u0026#34; \u0026gt; \u0026#34;$FOLDER_NAME/changelog.md\u0026#34; - gdrive files upload --recursive --parent \u0026#34;$DRIVE_PARENT\u0026#34; \u0026#34;$FOLDER_NAME\u0026#34; - rm android/app/aibeecara.jks android/key.properties File keystore dan key.properties sengaja dihapus di akhir fase post_build. Meskipun environment CodeBuild bersifat ephemeral dan dihancurkan setelah build selesai, menghapus file sensitif secara eksplisit adalah kebiasaan yang baik untuk menghindari risiko jika build environment dicache atau direuse.\nIt\u0026rsquo;s a Wrap! # Setelah semua konfigurasi selesai, nantinya, ketika ada push di branch yang telah ditentukan, maka Codebuild akan menjalankan build run baru secara otomatis.\nLog dari setiap build run dapat dilihat pada menu Developer Tools \u0026gt; CodeBuild \u0026gt; Build projects \u0026gt; nama build projects \u0026gt; nama build run \u0026gt; Build logs\nPipeline backend dan AI wrapper akan membuat task definition revision baru di setiap deployment. Task definition revision ini berisi image terbaru yang sudah dipush ke ECR dengan tag sesuai commit hash. Nantinya, ECS service akan diupdate untuk menggunakan revision baru tersebut.\nArtefak yang dihasilkan dari pipeline mobile akan dapat diakses melalui Google Drive yang sebelumnya telah dikonfigurasi.\nBerapa Ya, Biayanya? # Mari kita hitung estimasi biaya bulanan untuk ekosistem CI/CD ini, dengan asumsi penggunaan di region ap-southeast-3 Jakarta.\nEC2 t4g.small dibanderol seharga $0.0212 per jam di ap-southeast-3. Untuk 1 instance yang berjalan 24/7 selama 730 jam, maka:\n1 x $0.0212 x 730 = $15.48 per bulan ECR mengenakan $0.10 per GB per bulan. Dengan asumsi rata-rata 5GB storage untuk 2 repository yang masing-masing menyimpan 10 image terakhir.\n5 x $0.10 = $0.50 per bulan CodeBuild general1.small mengenakan $0.005 per menit. Dengan asumsi 300 menit build per bulan, dikurang dengan 100 menit free tier maka:\n(300 - 100) x $0.005 = $1.00 per bulan Secrets Manager mengenakan $0.40 per secret per bulan. Dengan 4 secret yang disimpan pada project Aibeecara, maka:\n5 x $0.40 = $2.00 per bulan Sehingga, dapat disimpulkan bahwa estimasi biayanya adalah:\nLayanan Penggunaan Biaya/Bulan ECS Control plane tidak dipungut biaya $0 EC2 1 instance t4g.small $15.48 ECR 5GB storage untuk 2 repository $0.50 CodeBuild 200 menit dengan general1.small $1.00 Secrets Manager 5 secrets $2.00 Total $18.98 Ingat bahwa estimasi tersebut belum termasuk biaya EBS, data transfer, dan Elastic IP. Jangan lupa juga bahwa biaya EC2 juga bisa lebih murah jika menggunakan Reserved Instances atau Savings Plans. Sebagai referensi, EC2 Instance Savings Plans untuk t4g.small dengan komitmen 1 tahun tanpa upfront bisa menghemat sekitar 37% dari harga on-demand.12\nPerbandingan harga ECS Fargate vs EC2\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nHarga AWS Secrets Manager\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nFormat ARN Secrets Manager di ECS\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nHarga Amazon ECR\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nLifecycle Policy di Amazon ECR\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nECS Scheduling Strategy DAEMON\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nHarga AWS CodeBuild\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nPrivileged Mode di CodeBuild untuk Docker Build\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nWebhook di AWS CodeBuild\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nRegister Task Definition\u0026#160;\u0026#x21a9;\u0026#xfe0e;\ngdrive CLI oleh Glotlabs\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSavings Plans untuk EC2\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"6 Januari 2026","externalUrl":null,"permalink":"/posts/ecs-ecosystem/","section":"Semua Artikel","summary":"AWS menyediakan banyak layanan untuk membangun ekosistem containerized application di cloud. Opsi deployment disediakan dari yang paling mudah di App Runner, hingga yang paling kompleks di EKS. Bahkan, untuk CI/CD saja, disediakan CodePipeline, CodeBuild, CodeDeploy, dan CodeCatalyst. Tapi pada kenyataannya, tidak semua project butuh orkestrasi yang kompleks. Kadang, ECS ditambah dengan CodeBuild buildspec.yaml custom saja sudah lebih dari cukup untuk menangani seluruh lifecycle containermu.\n","title":"Sebenarnya, ECS, ECR, Secrets Manager, dan CodeBuild Sudah Cukup untuk Ekosistem Containermu","type":"posts"},{"content":"","date":"18 November 2025","externalUrl":null,"permalink":"/tags/deepflow/","section":"Tags","summary":"","title":"Deepflow","type":"tags"},{"content":"","date":"18 November 2025","externalUrl":null,"permalink":"/tags/ebpf/","section":"Tags","summary":"","title":"EBPF","type":"tags"},{"content":"Deepflow adalah zero-instrumentation fullstack observability tools berbasis eBPF.\nZero-instrumentation artinya tools ini dapat diimplementasikan tanpa menyentuh source code aplikasi sama sekali.\nFullstack artinya tools ini dapat melakukan Application Performance Monitoring, Distributed Tracing, Continuous Profiling, sekaligus Host Metrics Monitoring. Singkatnya, Deepflow bisa memantau performa aplikasi sekaligus servernya.\nDeepflow memanfaatkan teknologi eBPF, yaitu sebuah teknologi untuk menjalankan program di kernel Linux. Sehingga, tools ini dapat mencatat aktivitas aplikasi dari level kernel, melalui network packet, request log, system call, function call, dll.\nFitur Host Metrics Monitoring Deepflow hanya tersedia di Enterprise Edition pada saat dokumentasi ini ditulis.\nUntungnya, Deepflow memiliki ekosistem yang sangat luas. Deepflow dapat diintegrasikan dengan perangkat open-source lainnya, seperti OpenTelemetry, Pyroscope, Prometheus, Telegraf, dan masih banyak lagi.\nUntuk use case Host Metrics monitoring, Telegraf menjadi pilihan utama karena simplicitynya.\nPrasyarat Hardware # Deepflow Server # Spesifikasi minimum : 2 vCPU 4GB RAM\nSpesifikasi rekomendasi : 4 vCPU 8GB RAM\nSpesifikasi penyimpanan default database Deepflow:\nTipe Data Ukuran/Baris Retensi Layer 7 Call Logs ~14 B 3 Hari Layer 4 Flow Logs ~30 B 3 Hari App Metrics ~3,4 B 7 Hari Network Metrics ~11 B 7 Hari File Events ~16 B 7 Hari Profiling ~7 B 3 Hari Retensi penyimpanan dapat dikonfigurasi sesuai dengan kebutuhan.\nDengan menggunakan konfigurasi di atas, maka dapat diperoleh hitungan kasar untuk kebutuhan penyimpanan skala kecil sebagai berikut:\nTipe Data Baris/Hari Ukuran/Baris Retensi Total Layer 7 Call Logs 5.000.000 ~70 B 3 Hari ~200 MB Layer 4 Flow Logs 500.000 ~150 B 3 Hari ~43 MB App Metrics 3.050.000 ~17 B 7 Hari ~69 MB Network Metrics 3.050.000 ~55 B 7 Hari ~224 MB File Events 15.000 ~80 B 7 Hari ~1,6 MB Profiling 15.000 ~35 B 3 Hari ~0,3 MB ~538 MB Pada kasus tersebut, storage minimum yang diperlukan untuk menyimpan isi database Deepflow server adalah 538 MB\nMaka, secara keseluruhan, storage minimum yang dibutuhkan oleh Deepflow server adalah:\nKomponen Ukuran Deepflow Server 1.42 GB Deepflow App 530 MB Clickhouse Server 408 MB Grafana 1.45 GB MySQL Server 785 MB Stella Agent (Opsional) 431 MB Penyimpanan Data 538 MB Total 5.08 GB Deepflow Agent # Idle Usage : 0.15vCPU 400 MB RAM\nDefault limit : 1vCPU 768 MB RAM\nPanduan Instalasi # Deepflow Server # Clone repositori Deepflow server dari Gitlab git clone https://gitlab.skwn.dev/hasbi-personal/deepflow-server Pindah ke dalam direktori Deepflow server cd deepflow-server Sesuaikan .env nano .env DEEPFLOW_VERSION=v7.1 NODE_IP_FOR_DEEPFLOW=#IP address host yang bisa diakses oleh Deepflow agent (Opsional) Jika ingin menggunakan fitur AI, tambahkan API key dan API base url di dalam konfigurasi nano common/config/stella-agent/df-llm-agent.yaml ... ai: enable: False # Ubah menjadi True platforms: # Jika menggunakan openai - platform: \u0026#34;openai\u0026#34; enable: False # Ubah menjadi True model: \u0026#34;openai\u0026#34; api_key: \u0026#39;\u0026#39; # Tambahkan API Key di sini engine_name: - \u0026#39;\u0026#39; # Tambahkan nama model AI di sini # Jika menggunakan azure - platform: \u0026#39;azure\u0026#39; enable: False # Ubah menjadi True model: \u0026#39;gpt\u0026#39; api_type: \u0026#39;azure\u0026#39; api_key: \u0026#39;\u0026#39; # Tambahkan API Key di sini api_base: \u0026#39;\u0026#39; # Tambahkan API base url di sini api_version: \u0026#39;\u0026#39; engine_name: - \u0026#39;\u0026#39; # Tambahkan nama model AI di sini # Jika menggunakan alibaba cloud model studio international (free) - platform: \u0026#39;aliyun\u0026#39; enable: False # Ubah menjadi True model: \u0026#39;dashscope\u0026#39; api_key: \u0026#39;\u0026#39; # Tambahkan API Key di sini api_base: \u0026#39;\u0026#39; # Tambahkan API base url di sini engine_name: - \u0026#39;qwen-max\u0026#39; - \u0026#39;qwen-plus-latest\u0026#39; Jalankan docker-compose up -d untuk menjalankan semua service Deepflow server docker compose up -d Tunggu 5 - 10 menit hingga server ready, lalu jalankan script setup untuk menambahkan konfigurasi agent chmod +x common/scripts/setup.sh ./common/scripts/setup.sh Deepflow Agent # Clone repositori Deepflow agent dari Gitlab git clone https://gitlab.skwn.dev/hasbi-personal/deepflow-agent Pindah ke dalam direktori Deepflow agent cd deepflow-agent Sesuaikan konfigurasi agent nano common/config/agent-config.yaml controller-ips: - 127.0.0.1 #IP address Deepflow server vtap-group-id-request: \u0026#39;g-deepflow12\u0026#39; Jalankan docker-compose up -d docker compose up -d Agent akan tersambung ke server dalam 5 - 10 menit setelah diaktifkan.\n","date":"18 November 2025","externalUrl":null,"permalink":"/posts/deepflow/","section":"Semua Artikel","summary":"Deepflow adalah zero-instrumentation fullstack observability tools berbasis eBPF.\nZero-instrumentation artinya tools ini dapat diimplementasikan tanpa menyentuh source code aplikasi sama sekali.\nFullstack artinya tools ini dapat melakukan Application Performance Monitoring, Distributed Tracing, Continuous Profiling, sekaligus Host Metrics Monitoring. Singkatnya, Deepflow bisa memantau performa aplikasi sekaligus servernya.\n","title":"eBPF Based Zero-Instrumentation Fullstack Observability dengan Deepflow","type":"posts"},{"content":"","date":"18 November 2025","externalUrl":null,"permalink":"/tags/monitoring/","section":"Tags","summary":"","title":"Monitoring","type":"tags"},{"content":"","date":"18 November 2025","externalUrl":null,"permalink":"/tags/observability/","section":"Tags","summary":"","title":"Observability","type":"tags"},{"content":"Wazuh adalah platform security open-source yang memiliki kapabilitas sebagai Security Information and Event Management (SIEM).\nSebagai SIEM, Wazuh bisa menjadi tempat penyimpanan dan pengolahan log yang tersentralisasi pada sistem SMSCrops, baik itu log dari sisi server maupun database. Log tersebut menyimpan informasi yang dianalisa Wazuh secara realtime untuk mendeteksi adanya security event.\nWazuh memiliki kapabilitas untuk meningkatkan visibility terhadap security event pada sistem SMSCrops. Kapabilitas tersebut didukung oleh fitur-fitur berikut:\nMalware Detection Untuk mendeteksi file atau proses berbahaya di dalam server. Vulnerability Detection Untuk mendeteksi celah keamanan pada package dan software di dalam server. Security Configuration Assessment Untuk mendeteksi miskonfigurasi dan memberikan saran remediasi sesuai best practices. Docker Monitoring Untuk memantau malicious activity pada docker container. File Integrity Monitoring (FIM) Untuk mendeteksi adanya pembuatan, modifikasi, atau penghapusan file. Who-Data Untuk mencatat pelaku yang membuat/memodifikasi/menghapus suatu file. Database Monitoring Untuk memantau eksekusi query pada database seperti MySQL. Email Alerting Untuk mengirimkan alert via email secara otomatis jika terdapat security event. Dengan adanya kedelapan fitur Wazuh tersebut, sistem SMSCrops bisa mendapatkan benefit sebagai berikut:\nMean Time to Detect (MTTD) dan Mean Time to Respond (MTTR) yang jauh lebih baik. Sebagai contoh, Wazuh dapat mendeteksi secara realtime query mencurigakan atau malicious file yang diupload ke server, dan segera mengirimkan alert ke tim terkait untuk segera ditangani. Forensik yang lebih baik, secara Wazuh mencatat what, who, when, where, dan how terkait suatu security event. Mengurangi attack surface dari segi teknologi dengan adanya rekomendasi system hardening dan patching dari Wazuh. Visibility dan traceability yang lebih baik terhadap security event. Wazuh mencatat setiap security event secara tersentralisasi, yang sangat membantu untuk menemukan attack pattern. Prasyarat Wazuh # Kebutuhan CPU \u0026amp; RAM # Komponen Spek Minimum Spek Rekomendasi Wazuh Indexer 2vCPU 4GB RAM 8vCPU 16GB RAM Wazuh Server 2vCPU 2GB RAM 4vCPU 8GB RAM Wazuh Dashboard 2vCPU 4GB RAM 4vCPU 8GB RAM Wazuh Agent - - Wazuh agent hanya mengonsumsi 35mb ram on average untuk beroperasi\nWazuh memberikan rekomendasi spesifikasi 4vCPU 8GB RAM untuk menajalankan ketiga komponen dalam satu server. Rekomendasi tersebut memiliki kapasitas berikut:\nMonitoring up to 100 endpoints Storing and indexing last 90 days logs Handling up to 25 agents Kebutuhan Penyimpanan Disk # Program Wazuh server, Wazuh indexer, dan Wazuh dashboard membutuhkan +-16GB ruang penyimpanan di dalam disk.\nJumlah ruang penyimpanan yang dibutuhkan untuk menyimpan log tergantung pada generated alerts per second (APS) dan jenis endpoint yang dimonitor.\nWazuh indexer Untuk setiap endpoint berjenis server (0.25 APS), storage yang dibutuhkan untuk menyimpan data di indexer selama 90 hari adalah 3.7GB.\nPenyimpanan indexer (per server/90 hari) Jumlah server Total 3.7GB 3 11.1GB Wazuh server Untuk setiap endpoint berjenis server (0.25 APS), storage yang dibutuhkan untuk menyimpan data di indexer selama 90 hari adalah 0.1GB.\nPenyimpanan indexer (per server/90 hari) Jumlah server Total 0.1GB 3 0.3 GB Sebagai contoh, untuk menjalankan sistem Wazuh setidaknya selama 1 tahun, maka diperlukan penyimpanan sebanyak:\n(11.1GB + 0.3GB) x 4 + 16GB = 61.6GB\nKebutuhan Sistem Operasi # Komponen wazuh membutuhkan 64-bit Intel, AMD, atau ARM Linux processor (x86_64/AMD64 atau AARCH64/ARM64) to run. Wazuh merekomendasikan beberapa sistem operasi berikut:\nAmazon Linux 2, Amazon Linux 2023 CentOS Stream 10 Red Hat Enterprise Linux 7, 8, 9, 10 Ubuntu 16.04, 18.04, 20.04, 22.04, 24.04 Instalasi Wazuh Manager # Wazuh menyediakan preconfigured Docker compose untuk memudahkan proses instalasi, baik itu untuk deployment single-node maupun multi-node. Oleh karena itu, Wazuh manager akan dipasang sebagai Docker container pada dokumentasi ini.\nWazuh indexer butuh untuk membuat banyak virtual memory area (VMA). VMA digunakan oleh Wazuh indexer untuk mengakses file pada disk tanpa harus memindahkan file tersebut ke dalam RAM terlebih dahulu. Maka dari itu, jumlah VMA pada host harus diperbesar dari 65530 menjadi 262144.\nsudo sysctl -w vm.max_map_count=262144 Example output:\nClone repositori wazuh-docker dengan versi spesifik. Versi terbaru saat dokumentasi ini dibuat adalah v4.14.3.\ngit clone https://github.com/wazuh/wazuh-docker.git -b v4.14.3 Example output:\nWorking directory untuk melakukan deployment Wazuh manager single-node ada di folder bernama single-node.\ncd wazuh-docker/single-node Example output:\nGenerate self-signed certificate untuk SSL, karena komunikasi antar komponen Wazuh dilakukan menggunakan protokol yang secure. Termasuk dashboard yang akan digunakan nantinya diakses menggunakana HTTPS.\ndocker compose -f generate-indexer-certs.yml run --rm generator Setelah selesai generate self-signed certificate, bisa langsung docker compose up.\ndocker compose up -d Setelah semua container up and running, butuh waktu 1-3 menit untuk Wazuh indexer selesai diinisiasi dan siap untuk beroperasi. Akses IP server menggunakan protokol https pada port yang telah ditentukan (defaultnya 443). Kredensial default untuk Wazuh dashboard adalah admin:SecretPassword.\nInstalasi Wazuh Agent # Server yang akan dimonitor harus dipasangi Wazuh agent untuk mengumpulkan data-data yang dibutuhkan dan kemudian diforward ke Wazuh manager.\nMeskipun Wazuh menyediakan preconfigured Docker compose untuk deployment agent, Wazuh agent akan mendapatkan visibilitas yang baik pada server host jika dideploy as a service. Wazuh agent memiliki beberapa keterbatasan jika dideploy sebagai docker container.\nPertama-tama, daftarkan repositori Wazuh pada package manager host.\nsudo apt install gnupg apt-transport-https curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | sudo gpg --no-default-keyring --keyring gnupg-ring:/usr/share/keyrings/wazuh.gpg --import \u0026amp;\u0026amp; sudo chmod 644 /usr/share/keyrings/wazuh.gpg echo \u0026#34;deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main\u0026#34; | sudo tee -a /etc/apt/sources.list.d/wazuh.lis Example output:\nInstal package wazuh-agent ditambah dengan membuat environment variable WAZUH_MANAGER yang berisi IP address dari Wazuh manager.\nWAZUH_MANAGER=\u0026#34;x.x.x.x\u0026#34; sudo -E apt install wazuh-agent Example output:\nSebelum mengaktifkan service Wazuh agent, sesuaikan konfigurasinya terlebih dahulu. File konfigurasi Wazuh agent secara default terdapat di /var/ossec/etc/ossec.conf.\nsudo nano /var/ossec/etc/ossec.conf Contoh konfigurasi dapat dilihat pada bagian Contoh Konfigurasi Default Wazuh Agent untuk Ubuntu 22.04 di bawah.\nAktifkan dan jalankan service wazuh agent untuk mulai menginisiasi komunikasi dengan Wazuh manager.\nsudo systemctl daemon-reload sudo systemctl enable wazuh-agent sudo systemctl start wazuh-agent Example output:\nContoh Konfigurasi Default Wazuh Agent untuk Ubuntu 22.04 # Berikut adalah contoh konfigurasi ossec.conf di server Ubuntu 22.04 (sesuaikan MANAGER_IP terlebih dahulu):\n\u0026lt;!-- Wazuh - Agent - General Configuration for Ubuntu 22.04 LTS (Jammy Jellyfish) --\u0026gt; \u0026lt;ossec_config\u0026gt; \u0026lt;client\u0026gt; \u0026lt;server\u0026gt; \u0026lt;address\u0026gt;MANAGER_IP\u0026lt;/address\u0026gt; \u0026lt;port\u0026gt;1514\u0026lt;/port\u0026gt; \u0026lt;protocol\u0026gt;tcp\u0026lt;/protocol\u0026gt; \u0026lt;/server\u0026gt; \u0026lt;config-profile\u0026gt;ubuntu, ubuntu22, ubuntu2204\u0026lt;/config-profile\u0026gt; \u0026lt;notify_time\u0026gt;20\u0026lt;/notify_time\u0026gt; \u0026lt;time-reconnect\u0026gt;60\u0026lt;/time-reconnect\u0026gt; \u0026lt;auto_restart\u0026gt;yes\u0026lt;/auto_restart\u0026gt; \u0026lt;crypto_method\u0026gt;aes\u0026lt;/crypto_method\u0026gt; \u0026lt;/client\u0026gt; \u0026lt;client_buffer\u0026gt; \u0026lt;disabled\u0026gt;no\u0026lt;/disabled\u0026gt; \u0026lt;queue_size\u0026gt;5000\u0026lt;/queue_size\u0026gt; \u0026lt;events_per_second\u0026gt;500\u0026lt;/events_per_second\u0026gt; \u0026lt;/client_buffer\u0026gt; \u0026lt;rootcheck\u0026gt; \u0026lt;disabled\u0026gt;no\u0026lt;/disabled\u0026gt; \u0026lt;check_files\u0026gt;yes\u0026lt;/check_files\u0026gt; \u0026lt;check_trojans\u0026gt;yes\u0026lt;/check_trojans\u0026gt; \u0026lt;check_dev\u0026gt;yes\u0026lt;/check_dev\u0026gt; \u0026lt;check_sys\u0026gt;yes\u0026lt;/check_sys\u0026gt; \u0026lt;check_pids\u0026gt;yes\u0026lt;/check_pids\u0026gt; \u0026lt;check_ports\u0026gt;yes\u0026lt;/check_ports\u0026gt; \u0026lt;check_if\u0026gt;yes\u0026lt;/check_if\u0026gt; \u0026lt;frequency\u0026gt;43200\u0026lt;/frequency\u0026gt; \u0026lt;rootkit_files\u0026gt;etc/shared/rootkit_files.txt\u0026lt;/rootkit_files\u0026gt; \u0026lt;rootkit_trojans\u0026gt;etc/shared/rootkit_trojans.txt\u0026lt;/rootkit_trojans\u0026gt; \u0026lt;skip_nfs\u0026gt;yes\u0026lt;/skip_nfs\u0026gt; \u0026lt;/rootcheck\u0026gt; \u0026lt;wodle name=\u0026#34;cis-cat\u0026#34;\u0026gt; \u0026lt;disabled\u0026gt;yes\u0026lt;/disabled\u0026gt; \u0026lt;timeout\u0026gt;1800\u0026lt;/timeout\u0026gt; \u0026lt;interval\u0026gt;1d\u0026lt;/interval\u0026gt; \u0026lt;scan-on-start\u0026gt;yes\u0026lt;/scan-on-start\u0026gt; \u0026lt;java_path\u0026gt;wodles/java\u0026lt;/java_path\u0026gt; \u0026lt;ciscat_path\u0026gt;wodles/ciscat\u0026lt;/ciscat_path\u0026gt; \u0026lt;/wodle\u0026gt; \u0026lt;wodle name=\u0026#34;osquery\u0026#34;\u0026gt; \u0026lt;disabled\u0026gt;yes\u0026lt;/disabled\u0026gt; \u0026lt;run_daemon\u0026gt;yes\u0026lt;/run_daemon\u0026gt; \u0026lt;log_path\u0026gt;/var/log/osquery/osqueryd.results.log\u0026lt;/log_path\u0026gt; \u0026lt;config_path\u0026gt;/etc/osquery/osquery.conf\u0026lt;/config_path\u0026gt; \u0026lt;add_labels\u0026gt;yes\u0026lt;/add_labels\u0026gt; \u0026lt;/wodle\u0026gt; \u0026lt;wodle name=\u0026#34;syscollector\u0026#34;\u0026gt; \u0026lt;disabled\u0026gt;no\u0026lt;/disabled\u0026gt; \u0026lt;interval\u0026gt;1h\u0026lt;/interval\u0026gt; \u0026lt;scan_on_start\u0026gt;yes\u0026lt;/scan_on_start\u0026gt; \u0026lt;hardware\u0026gt;yes\u0026lt;/hardware\u0026gt; \u0026lt;os\u0026gt;yes\u0026lt;/os\u0026gt; \u0026lt;network\u0026gt;yes\u0026lt;/network\u0026gt; \u0026lt;packages\u0026gt;yes\u0026lt;/packages\u0026gt; \u0026lt;ports all=\u0026#34;yes\u0026#34;\u0026gt;yes\u0026lt;/ports\u0026gt; \u0026lt;processes\u0026gt;yes\u0026lt;/processes\u0026gt; \u0026lt;users\u0026gt;yes\u0026lt;/users\u0026gt; \u0026lt;groups\u0026gt;yes\u0026lt;/groups\u0026gt; \u0026lt;services\u0026gt;yes\u0026lt;/services\u0026gt; \u0026lt;synchronization\u0026gt; \u0026lt;max_eps\u0026gt;10\u0026lt;/max_eps\u0026gt; \u0026lt;/synchronization\u0026gt; \u0026lt;/wodle\u0026gt; \u0026lt;sca\u0026gt; \u0026lt;enabled\u0026gt;yes\u0026lt;/enabled\u0026gt; \u0026lt;scan_on_start\u0026gt;yes\u0026lt;/scan_on_start\u0026gt; \u0026lt;interval\u0026gt;12h\u0026lt;/interval\u0026gt; \u0026lt;skip_nfs\u0026gt;yes\u0026lt;/skip_nfs\u0026gt; \u0026lt;/sca\u0026gt; \u0026lt;syscheck\u0026gt; \u0026lt;disabled\u0026gt;no\u0026lt;/disabled\u0026gt; \u0026lt;frequency\u0026gt;43200\u0026lt;/frequency\u0026gt; \u0026lt;scan_on_start\u0026gt;yes\u0026lt;/scan_on_start\u0026gt; \u0026lt;!-- Direktori sistem dasar — relevan untuk semua Ubuntu 22.04 --\u0026gt; \u0026lt;directories realtime=\u0026#34;yes\u0026#34;\u0026gt;/etc,/usr/bin,/usr/sbin\u0026lt;/directories\u0026gt; \u0026lt;directories realtime=\u0026#34;yes\u0026#34;\u0026gt;/bin,/sbin,/boot\u0026lt;/directories\u0026gt; \u0026lt;directories realtime=\u0026#34;yes\u0026#34;\u0026gt;/root\u0026lt;/directories\u0026gt; \u0026lt;directories realtime=\u0026#34;yes\u0026#34;\u0026gt;/var/spool/cron\u0026lt;/directories\u0026gt; \u0026lt;ignore\u0026gt;/etc/mtab\u0026lt;/ignore\u0026gt; \u0026lt;ignore\u0026gt;/etc/hosts.deny\u0026lt;/ignore\u0026gt; \u0026lt;ignore\u0026gt;/etc/mail/statistics\u0026lt;/ignore\u0026gt; \u0026lt;ignore\u0026gt;/etc/random-seed\u0026lt;/ignore\u0026gt; \u0026lt;ignore\u0026gt;/etc/random.seed\u0026lt;/ignore\u0026gt; \u0026lt;ignore\u0026gt;/etc/adjtime\u0026lt;/ignore\u0026gt; \u0026lt;ignore\u0026gt;/etc/httpd/logs\u0026lt;/ignore\u0026gt; \u0026lt;ignore\u0026gt;/etc/utmpx\u0026lt;/ignore\u0026gt; \u0026lt;ignore\u0026gt;/etc/wtmpx\u0026lt;/ignore\u0026gt; \u0026lt;ignore\u0026gt;/etc/cups/certs\u0026lt;/ignore\u0026gt; \u0026lt;ignore\u0026gt;/etc/dumpdates\u0026lt;/ignore\u0026gt; \u0026lt;ignore\u0026gt;/etc/svc/volatile\u0026lt;/ignore\u0026gt; \u0026lt;ignore\u0026gt;/etc/apparmor.d/cache.d\u0026lt;/ignore\u0026gt; \u0026lt;ignore type=\u0026#34;sregex\u0026#34;\u0026gt;.log$|.swp$\u0026lt;/ignore\u0026gt; \u0026lt;nodiff\u0026gt;/etc/ssl/private.key\u0026lt;/nodiff\u0026gt; \u0026lt;skip_nfs\u0026gt;yes\u0026lt;/skip_nfs\u0026gt; \u0026lt;skip_dev\u0026gt;yes\u0026lt;/skip_dev\u0026gt; \u0026lt;skip_proc\u0026gt;yes\u0026lt;/skip_proc\u0026gt; \u0026lt;skip_sys\u0026gt;yes\u0026lt;/skip_sys\u0026gt; \u0026lt;process_priority\u0026gt;10\u0026lt;/process_priority\u0026gt; \u0026lt;max_eps\u0026gt;50\u0026lt;/max_eps\u0026gt; \u0026lt;synchronization\u0026gt; \u0026lt;enabled\u0026gt;yes\u0026lt;/enabled\u0026gt; \u0026lt;interval\u0026gt;5m\u0026lt;/interval\u0026gt; \u0026lt;max_eps\u0026gt;10\u0026lt;/max_eps\u0026gt; \u0026lt;/synchronization\u0026gt; \u0026lt;/syscheck\u0026gt; \u0026lt;active-response\u0026gt; \u0026lt;disabled\u0026gt;no\u0026lt;/disabled\u0026gt; \u0026lt;ca_store\u0026gt;etc/wpk_root.pem\u0026lt;/ca_store\u0026gt; \u0026lt;ca_verification\u0026gt;yes\u0026lt;/ca_verification\u0026gt; \u0026lt;/active-response\u0026gt; \u0026lt;logging\u0026gt; \u0026lt;log_format\u0026gt;plain\u0026lt;/log_format\u0026gt; \u0026lt;/logging\u0026gt; \u0026lt;/ossec_config\u0026gt; \u0026lt;ossec_config\u0026gt; \u0026lt;localfile\u0026gt; \u0026lt;log_format\u0026gt;journald\u0026lt;/log_format\u0026gt; \u0026lt;location\u0026gt;journald\u0026lt;/location\u0026gt; \u0026lt;/localfile\u0026gt; \u0026lt;localfile\u0026gt; \u0026lt;log_format\u0026gt;syslog\u0026lt;/log_format\u0026gt; \u0026lt;location\u0026gt;/var/log/syslog\u0026lt;/location\u0026gt; \u0026lt;/localfile\u0026gt; \u0026lt;localfile\u0026gt; \u0026lt;log_format\u0026gt;syslog\u0026lt;/log_format\u0026gt; \u0026lt;location\u0026gt;/var/log/auth.log\u0026lt;/location\u0026gt; \u0026lt;/localfile\u0026gt; \u0026lt;localfile\u0026gt; \u0026lt;log_format\u0026gt;syslog\u0026lt;/log_format\u0026gt; \u0026lt;location\u0026gt;/var/ossec/logs/active-responses.log\u0026lt;/location\u0026gt; \u0026lt;/localfile\u0026gt; \u0026lt;localfile\u0026gt; \u0026lt;log_format\u0026gt;syslog\u0026lt;/log_format\u0026gt; \u0026lt;location\u0026gt;/var/log/dpkg.log\u0026lt;/location\u0026gt; \u0026lt;/localfile\u0026gt; \u0026lt;/ossec_config\u0026gt; Konfigurasi ini sudah mencakup hal-hal berikut:\nKoneksi ke Wazuh manager, termasuk IP address, port, enkripsi yang dipakai, dan konfigurasi buffering koneksi. Threat detecting configuration, yaitu rootcheck untuk mendeteksi rootkit, trojan, file mencurigakan, port tersembunyi, dan perubahan di /dev dan /sys. Security Configuration Assessment (SCA) untuk mengecek apakah konfigurasi sistem sesuai best practices CIS benchmark. File Integrity Monitoring (FIM) untuk memantau perubahan file secara real-time pada folder-folder penting, yaitu /etc, /usr/bin, /usr/sbin, /bin, /sbin, /boot, /root, dan /var/spool/cron. File yang sering berubah seperti /etc/mtab, /etc/adjtime, file .log dan .swp diignore saja supaya lognya tidak overflow. Log monitoring dari journald, /var/log/syslog, /var/log/auth.log, /var/log/dpkg.log, dan active-response.log. Syscollector untuk inventarisasi hardware, OS, packet, port, proses, dan user yang ada pada server. Panduan Konfigurasi Wazuh Agent # Konfigurasi sebelumnya masih dapat diextend untuk disesuaikan di setiap environment. Berikut hal-hal yang dapat ditambahkan pada konfigurasi Wazuh agent:\nMonitoring Log File Gunakan elemen untuk memberitau Wazuh agent bahwa ada file log yang perlu dibaca dan dikirim ke Manager untuk dianalisis.\n\u0026lt;localfile\u0026gt; \u0026lt;log_format\u0026gt;FORMAT\u0026lt;/log_format\u0026gt; \u0026lt;location\u0026gt;/path/ke/file.log\u0026lt;/location\u0026gt; \u0026lt;/localfile\u0026gt; FORMAT bisa berupa syslog, json, apache, audit, multi-line, journald, command, dan full-command.\nScheduled Command Execution Gunakan dengan log_format bernilai command atau full_command. Wazuh akan menjalankan command ini secara berkala dan mengirim hasilnya ke Manager.\n\u0026lt;localfile\u0026gt; \u0026lt;log_format\u0026gt;full_command\u0026lt;/log_format\u0026gt; \u0026lt;command\u0026gt;COMMAND\u0026lt;/command\u0026gt; \u0026lt;frequency\u0026gt;SECONDS\u0026lt;/frequency\u0026gt; \u0026lt;/localfile\u0026gt; File Integrity Monitoring Tambahkan elemen di dalam blok yang sudah ada untuk memonitor direktori tersebut menggunakan FIM.\n\u0026lt;syscheck\u0026gt; \u0026lt;directories realtime=\u0026#34;yes\u0026#34;\u0026gt;/path/ke/direktori\u0026lt;/directories\u0026gt; \u0026lt;/syscheck\u0026gt; Ignore Element Tambahkan elemen di dalam blok atau supaya file/folder tersebut tidak dimonitoring oleh FIM atau rootcheck. Sehingga tidak terjadi false positive dari direktori yang isinya memang terus berubah seperti cache dan runtime data.\n\u0026lt;syscheck\u0026gt; \u0026lt;ignore\u0026gt;/path/ke/file/direktori\u0026lt;/ignore\u0026gt; \u0026lt;ignore type=\u0026#34;sregex\u0026#34;\u0026gt;REGEX\u0026lt;/ignore\u0026gt; \u0026lt;/syscheck\u0026gt; \u0026lt;rootcheck\u0026gt; \u0026lt;ignore\u0026gt;/path/ke/file/direktori\u0026lt;/ignore\u0026gt; \u0026lt;ignore type=\u0026#34;sregex\u0026#34;\u0026gt;REGEX\u0026lt;/ignore\u0026gt; \u0026lt;/rootcheck\u0026gt; Wodle Wodle adalah modul opsional Wazuh agent. Aktifkan dengan mengubah menjadi no.\n\u0026lt;wodle name=\u0026#34;docker-listener\u0026#34;\u0026gt; \u0026lt;disabled\u0026gt;no\u0026lt;/disabled\u0026gt; \u0026lt;/wodle\u0026gt; \u0026lt;wodle name=\u0026#34;osquery\u0026#34;\u0026gt; \u0026lt;disabled\u0026gt;no\u0026lt;/disabled\u0026gt; \u0026lt;run_daemon\u0026gt;yes\u0026lt;/run_daemon\u0026gt; \u0026lt;log_path\u0026gt;/var/log/osquery/osqueryd.results.log\u0026lt;/log_path\u0026gt; \u0026lt;config_path\u0026gt;/etc/osquery/osquery.conf\u0026lt;/config_path\u0026gt; \u0026lt;add_labels\u0026gt;yes\u0026lt;/add_labels\u0026gt; \u0026lt;/wodle\u0026gt; \u0026lt;wodle name=\u0026#34;cis-cat\u0026#34;\u0026gt; \u0026lt;disabled\u0026gt;no\u0026lt;/disabled\u0026gt; \u0026lt;timeout\u0026gt;1800\u0026lt;/timeout\u0026gt; \u0026lt;interval\u0026gt;1d\u0026lt;/interval\u0026gt; \u0026lt;scan-on-start\u0026gt;yes\u0026lt;/scan-on-start\u0026gt; \u0026lt;java_path\u0026gt;wodles/java\u0026lt;/java_path\u0026gt; \u0026lt;ciscat_path\u0026gt;wodles/ciscat\u0026lt;/ciscat_path\u0026gt; \u0026lt;/wodle\u0026gt; Malware Detection Tambahkan element pada konfigurasi agent untuk mengaktifkan malware detection. Berikut adalah contoh konfigurasi default rootcheck untuk Linux.\n\u0026lt;rootcheck\u0026gt; \u0026lt;disabled\u0026gt;no\u0026lt;/disabled\u0026gt; \u0026lt;check_unixaudit\u0026gt;yes\u0026lt;/check_unixaudit\u0026gt; \u0026lt;check_files\u0026gt;yes\u0026lt;/check_files\u0026gt; \u0026lt;check_trojans\u0026gt;yes\u0026lt;/check_trojans\u0026gt; \u0026lt;check_dev\u0026gt;yes\u0026lt;/check_dev\u0026gt; \u0026lt;check_sys\u0026gt;yes\u0026lt;/check_sys\u0026gt; \u0026lt;check_pids\u0026gt;yes\u0026lt;/check_pids\u0026gt; \u0026lt;check_ports\u0026gt;yes\u0026lt;/check_ports\u0026gt; \u0026lt;check_if\u0026gt;yes\u0026lt;/check_if\u0026gt; \u0026lt;ignore type=\u0026#34;sregex\u0026#34;\u0026gt;^/etc/\u0026lt;/ignore\u0026gt; \u0026lt;frequency\u0026gt;43200\u0026lt;/frequency\u0026gt; \u0026lt;rootkit_files\u0026gt;etc/shared/rootkit_files.txt\u0026lt;/rootkit_files\u0026gt; \u0026lt;rootkit_trojans\u0026gt;etc/shared/rootkit_trojans.txt\u0026lt;/rootkit_trojans\u0026gt; \u0026lt;skip_nfs\u0026gt;yes\u0026lt;/skip_nfs\u0026gt; \u0026lt;/rootcheck\u0026gt; Security Configuration Assessment Gunakan element untuk mengaktifkan modul configuration assessment. Berikut adalah contoh konfigurasi default untuk sca.\n\u0026lt;sca\u0026gt; \u0026lt;enabled\u0026gt;yes\u0026lt;/enabled\u0026gt; \u0026lt;scan_on_start\u0026gt;yes\u0026lt;/scan_on_start\u0026gt; \u0026lt;interval\u0026gt;12h\u0026lt;/interval\u0026gt; \u0026lt;skip_nfs\u0026gt;yes\u0026lt;/skip_nfs\u0026gt; \u0026lt;/sca\u0026gt; Konfigurasi FIM + Who-Data # Modul File Integrity Monitoring pada defaultnya tidak mencatat informasi tentang siapa yang melakukan modifikasi pada file yang dimonitor. Konfigurasi who-data membantu mencatat informasi user, program, dan proses yang terlibat dalam modifikasi suatu file.\nSalah satu implementasi who-data adalah menggunakan audit mode, yaitu ekstensi FIM untuk Realtime Monitoring dengan Linux Audit subsystem. Konfigurasi ini dilakukan pada tiap wazuh agent.\nPertama, pasang package auditd terlebih dahulu.\nsudo apt install auditd Example output:\nPasang juga package plugin audispd af_unix melalui package audispd-plugins dan restart service auditd.\nsudo apt install audispd-plugin Example output:\nPada /var/ossec/etc/ossec.conf milik agent, tambahkan element provider dengan value audit di dalam element whodata. Bagian ini diletakkan di dalam element syscheck.\nsudo nano /var/ossec/etc/ossec.conf \u0026lt;syscheck\u0026gt; ... \u0026lt;whodata\u0026gt; \u0026lt;provider\u0026gt;audit\u0026lt;/provider\u0026gt; \u0026lt;/whodata\u0026gt; \u0026lt;/syscheck\u0026gt; Untuk setiap direktori yang akan dimonitor, tambahkan konfigurasi realtime=yes dan whodata=yes pada element directori\n\u0026lt;directories realtime=\u0026#34;yes\u0026#34; whodata=\u0026#34;yes\u0026#34;\u0026gt;/path/ke/direktori\u0026lt;/directories\u0026gt; Restart service wazuh agent untuk apply perubahan.\nsudo systemctl restart wazuh-agent Example output:\nGunakan auditctl untuk memverifikasi rules dari konfigurasi wazuh agent sudah masuk ke auditd. Apabila sudah sesuai, maka akan ditampilkan output \u0026ldquo;-w -p wa -k wazuh_fim\u0026rdquo; untuk setiap direktori yang dimonitor.\nsudo auditctl -l | grep wazuh_fim Example output:\nKonfigurasi Email Alerting untuk FIM # Server yang disediakan oleh cloud provider biasanya tidak memperbolehkan traffic SMTP outbound. Alternatif yang dapat digunakan untuk mengirim email adalah external API integration. Wazuh mendukung beberapa external API secara native, seperti Slack, Virustotal, dll. Wazuh juga mendukung custom external API integration menggunakan script python.\nDokumentasi ini menggunakan Brevo sebagai Email Service Providernya karena mudah digunakan dan free limitnya banyak (300 email/hari). Pertama, buat akun di https://onboarding.brevo.com/account/register. Selesaikan proses pengisian data diri dan pilih free tier.\nKemudian, pada Settings \u0026gt; SMTP \u0026amp; API \u0026gt; API keys \u0026amp; MCP, pilih Generate a new API key. Kemudian generate dan simpan API key-nya untuk digunakana nanti.\nSelanjutnya, buat file dengan nama custom-brevo yang berisi script untuk memparsing dan mengirim email ke API Brevo. Script untuk custom external API integration harus dimulai dengan prefic custom-. Letakkan scriptnya di folder wazuh-docker/single-node/config/wazuh_cluster/integrations/\ncd wazuh-docker/single-node sudo nano config/wazuh_cluster/integrations/custom-brevo Berikut adalah contoh script custom-brevo. Jangan lupa untuk menyesuaikan EMAIL_BREVO, EMAIL_TUJUAN, dan NAMA_PENERIMA.\n#!/usr/bin/env python3 import json import sys import time from urllib.request import Request, urlopen from urllib.error import HTTPError, URLError SENDER_EMAIL = \u0026#34;EMAIL_BREVO\u0026#34; SENDER_NAME = \u0026#34;Wazuh Alert\u0026#34; RECIPIENT_LIST = [ {\u0026#34;email\u0026#34;: \u0026#34;EMAIL_TUJUAN1\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;NAMA_PENERIMA\u0026#34;}, {\u0026#34;email\u0026#34;: \u0026#34;EMAIL_TUJUAN2\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;NAMA_PENERIMA\u0026#34;}, ] SUBJECT_PREFIX = \u0026#34;[Wazuh]\u0026#34; LOG_FILE = \u0026#34;/var/ossec/logs/integrations.log\u0026#34; def log(msg): timestamp = time.strftime(\u0026#34;%Y-%m-%d %H:%M:%S\u0026#34;, time.localtime()) entry = \u0026#34;{} custom-brevo: {}\\n\u0026#34;.format(timestamp, msg) try: with open(LOG_FILE, \u0026#34;a\u0026#34;) as f: f.write(entry) except Exception: pass def read_alert(alert_file_path): try: with open(alert_file_path) as f: alert = json.load(f) return alert except Exception as e: log(\u0026#34;ERROR: Gagal membaca alert file {}: {}\u0026#34;.format(alert_file_path, e)) sys.exit(1) def build_subject(alert): rule = alert.get(\u0026#34;rule\u0026#34;, {}) rule_level = rule.get(\u0026#34;level\u0026#34;, 0) description = rule.get(\u0026#34;description\u0026#34;, \u0026#34;Wazuh Alert\u0026#34;) if rule_level \u0026gt;= 12: severity = \u0026#34;CRITICAL\u0026#34; elif rule_level \u0026gt;= 8: severity = \u0026#34;HIGH\u0026#34; elif rule_level \u0026gt;= 5: severity = \u0026#34;MEDIUM\u0026#34; else: severity = \u0026#34;LOW\u0026#34; return \u0026#34;{} [{}] {}\u0026#34;.format(SUBJECT_PREFIX, severity, description) def build_html(alert): rule = alert.get(\u0026#34;rule\u0026#34;, {}) agent = alert.get(\u0026#34;agent\u0026#34;, {}) syscheck = alert.get(\u0026#34;syscheck\u0026#34;, {}) audit = syscheck.get(\u0026#34;audit\u0026#34;, {}) timestamp = alert.get(\u0026#34;timestamp\u0026#34;, \u0026#34;-\u0026#34;) rule_level = rule.get(\u0026#34;level\u0026#34;, 0) rule_desc = rule.get(\u0026#34;description\u0026#34;, \u0026#34;-\u0026#34;) agent_name = agent.get(\u0026#34;name\u0026#34;, \u0026#34;-\u0026#34;) agent_ip = agent.get(\u0026#34;ip\u0026#34;, \u0026#34;-\u0026#34;) file_path = syscheck.get(\u0026#34;path\u0026#34;, \u0026#34;\u0026#34;) file_event = syscheck.get(\u0026#34;event\u0026#34;, \u0026#34;\u0026#34;) changed_attrs = syscheck.get(\u0026#34;changed_attributes\u0026#34;, []) size_before = syscheck.get(\u0026#34;size_before\u0026#34;, \u0026#34;\u0026#34;) size_after = syscheck.get(\u0026#34;size_after\u0026#34;, \u0026#34;\u0026#34;) diff = syscheck.get(\u0026#34;diff\u0026#34;, \u0026#34;\u0026#34;) login_user = audit.get(\u0026#34;login_user\u0026#34;, {}).get(\u0026#34;name\u0026#34;, \u0026#34;\u0026#34;) effective_user = audit.get(\u0026#34;effective_user\u0026#34;, {}).get(\u0026#34;name\u0026#34;, \u0026#34;\u0026#34;) process_name = audit.get(\u0026#34;process\u0026#34;, {}).get(\u0026#34;name\u0026#34;, \u0026#34;\u0026#34;) process_cwd = audit.get(\u0026#34;process\u0026#34;, {}).get(\u0026#34;cwd\u0026#34;, \u0026#34;\u0026#34;) if rule_level \u0026gt;= 12: color = \u0026#34;#c0392b\u0026#34; elif rule_level \u0026gt;= 8: color = \u0026#34;#d35400\u0026#34; elif rule_level \u0026gt;= 5: color = \u0026#34;#e67e22\u0026#34; else: color = \u0026#34;#2980b9\u0026#34; ROW = \u0026#39;\u0026lt;tr{bg}\u0026gt;\u0026lt;td style=\u0026#34;padding:6px 12px;font-weight:bold;width:130px;color:#555;\u0026#34;\u0026gt;{label}\u0026lt;/td\u0026gt;\u0026lt;td style=\u0026#34;padding:6px 12px;\u0026#34;\u0026gt;{value}\u0026lt;/td\u0026gt;\u0026lt;/tr\u0026gt;\u0026#39; ZEBRA = \u0026#39; style=\u0026#34;background:#f9f9f9;\u0026#34;\u0026#39; SEPARATOR = \u0026#39;\u0026lt;tr\u0026gt;\u0026lt;td colspan=\u0026#34;2\u0026#34; style=\u0026#34;border-top:1px solid #eee;padding:0;\u0026#34;\u0026gt;\u0026lt;/td\u0026gt;\u0026lt;/tr\u0026gt;\u0026#39; rows = [] if file_path: rows.append(ROW.format(bg=\u0026#34;\u0026#34;, label=\u0026#34;File\u0026#34;, value=\u0026#34;\u0026lt;code\u0026gt;{}\u0026lt;/code\u0026gt;\u0026#34;.format(file_path))) rows.append(ROW.format(bg=ZEBRA, label=\u0026#34;Event\u0026#34;, value=file_event)) if changed_attrs: display_attrs = [a for a in changed_attrs if a not in (\u0026#34;md5\u0026#34;, \u0026#34;sha1\u0026#34;, \u0026#34;sha256\u0026#34;)] if display_attrs: rows.append(ROW.format(bg=\u0026#34;\u0026#34;, label=\u0026#34;Changed\u0026#34;, value=\u0026#34;, \u0026#34;.join(display_attrs))) if size_before and size_after: rows.append(ROW.format(bg=ZEBRA, label=\u0026#34;Size\u0026#34;, value=\u0026#34;{} \u0026amp;rarr; {} bytes\u0026#34;.format(size_before, size_after))) if diff: rows.append(\u0026#39;\u0026lt;tr\u0026gt;\u0026lt;td style=\u0026#34;padding:6px 12px;font-weight:bold;color:#555;\u0026#34;\u0026gt;Diff\u0026lt;/td\u0026gt;\u0026lt;td style=\u0026#34;padding:6px 12px;\u0026#34;\u0026gt;\u0026lt;pre style=\u0026#34;background:#2d2d2d;color:#f8f8f2;padding:8px;border-radius:4px;font-size:12px;overflow-x:auto;\u0026#34;\u0026gt;{}\u0026lt;/pre\u0026gt;\u0026lt;/td\u0026gt;\u0026lt;/tr\u0026gt;\u0026#39;.format(diff)) if login_user or process_name: rows.append(SEPARATOR) if login_user: user_display = login_user if effective_user and effective_user != login_user: user_display = \u0026#34;{} \u0026amp;rarr; {} (sudo)\u0026#34;.format(login_user, effective_user) rows.append(ROW.format(bg=\u0026#34;\u0026#34;, label=\u0026#34;User\u0026#34;, value=\u0026#34;\u0026lt;strong\u0026gt;{}\u0026lt;/strong\u0026gt;\u0026#34;.format(user_display))) elif effective_user: rows.append(ROW.format(bg=\u0026#34;\u0026#34;, label=\u0026#34;User\u0026#34;, value=effective_user)) if process_name: rows.append(ROW.format(bg=ZEBRA, label=\u0026#34;Process\u0026#34;, value=\u0026#34;\u0026lt;code\u0026gt;{}\u0026lt;/code\u0026gt;\u0026#34;.format(process_name))) if process_cwd: rows.append(ROW.format(bg=\u0026#34;\u0026#34;, label=\u0026#34;Working Dir\u0026#34;, value=\u0026#34;\u0026lt;code\u0026gt;{}\u0026lt;/code\u0026gt;\u0026#34;.format(process_cwd))) rows.append(SEPARATOR) rows.append(ROW.format(bg=\u0026#34;\u0026#34;, label=\u0026#34;Agent\u0026#34;, value=\u0026#34;{} ({})\u0026#34;.format(agent_name, agent_ip))) rows.append(ROW.format(bg=ZEBRA, label=\u0026#34;Time\u0026#34;, value=timestamp)) table_rows = \u0026#34;\\n \u0026#34;.join(rows) html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;meta charset=\u0026#34;utf-8\u0026#34;\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body style=\u0026#34;font-family:Arial,Helvetica,sans-serif;margin:0;padding:0;background:#f4f4f4;\u0026#34;\u0026gt; \u0026lt;div style=\u0026#34;max-width:600px;margin:20px auto;background:#fff;border-radius:8px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.1);\u0026#34;\u0026gt; \u0026lt;div style=\u0026#34;background:{color};color:#fff;padding:14px 20px;\u0026#34;\u0026gt; \u0026lt;p style=\u0026#34;margin:0;font-size:16px;font-weight:bold;\u0026#34;\u0026gt;{desc}\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;table style=\u0026#34;width:100%;border-collapse:collapse;font-size:14px;\u0026#34;\u0026gt; {table_rows} \u0026lt;/table\u0026gt; \u0026lt;div style=\u0026#34;background:#ecf0f1;padding:8px 20px;font-size:11px;color:#95a5a6;text-align:center;\u0026#34;\u0026gt; Wazuh FIM Alert \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34;.format( color=color, desc=rule_desc, table_rows=table_rows, ) return html def send_email(api_key, hook_url, subject, html_content): payload = { \u0026#34;sender\u0026#34;: { \u0026#34;name\u0026#34;: SENDER_NAME, \u0026#34;email\u0026#34;: SENDER_EMAIL, }, \u0026#34;to\u0026#34;: RECIPIENT_LIST, \u0026#34;subject\u0026#34;: subject, \u0026#34;htmlContent\u0026#34;: html_content, } data = json.dumps(payload).encode(\u0026#34;utf-8\u0026#34;) req = Request(hook_url, data=data) req.add_header(\u0026#34;accept\u0026#34;, \u0026#34;application/json\u0026#34;) req.add_header(\u0026#34;content-type\u0026#34;, \u0026#34;application/json\u0026#34;) req.add_header(\u0026#34;api-key\u0026#34;, api_key) try: response = urlopen(req) response_body = response.read().decode(\u0026#34;utf-8\u0026#34;) log(\u0026#34;OK: Email terkirim. Response: {}\u0026#34;.format(response_body)) except HTTPError as e: error_body = e.read().decode(\u0026#34;utf-8\u0026#34;) if hasattr(e, \u0026#34;read\u0026#34;) else str(e) log(\u0026#34;ERROR: HTTP {} - {}\u0026#34;.format(e.code, error_body)) sys.exit(1) except URLError as e: log(\u0026#34;ERROR: Koneksi gagal - {}\u0026#34;.format(e.reason)) sys.exit(1) except Exception as e: log(\u0026#34;ERROR: UnExample - {}\u0026#34;.format(e)) sys.exit(1) def main(): if len(sys.argv) \u0026lt; 4: log(\u0026#34;ERROR: Argumen tidak lengkap. Usage: custom-brevo \u0026lt;alert_file\u0026gt; \u0026lt;api_key\u0026gt; \u0026lt;hook_url\u0026gt;\u0026#34;) sys.exit(1) alert_file_path = sys.argv[1] api_key = sys.argv[2] hook_url = sys.argv[3] alert = read_alert(alert_file_path) subject = build_subject(alert) html = build_html(alert) log(\u0026#34;Mengirim email untuk rule {} (level {})...\u0026#34;.format( alert.get(\u0026#34;rule\u0026#34;, {}).get(\u0026#34;id\u0026#34;, \u0026#34;?\u0026#34;), alert.get(\u0026#34;rule\u0026#34;, {}).get(\u0026#34;level\u0026#34;, \u0026#34;?\u0026#34;), )) send_email(api_key, hook_url, subject, html) if __name__ == \u0026#34;__main__\u0026#34;: main() Script di atas betrujuan untuk membaca file JSON alert dari rule yang tertrigger, kemudian membuat subject email berdasarkan severity (LOW/MEDIUM/HIGH/CRITICAL). Isi dari emailnya berupa HTML yang berisi detail perubahan file, yaitu path, event, diff, user, process, info agent. Email akan dikirim ke daftar penerima yang tertera.\nSelanjutnya, melalui menu Server Management \u0026gt; Rules di Wazuh dashboard, tulis rules baru untuk menyaring event yang dibutuhkan untuk mentrigger email alerting.\nBerikut adalah contoh rules untuk menyaring event modifikasi pada file:\n\u0026lt;group name=\u0026#34;fim_email\u0026#34;\u0026gt; \u0026lt;rule id=\u0026#34;100101\u0026#34; level=\u0026#34;10\u0026#34;\u0026gt; \u0026lt;if_sid\u0026gt;550,553,554\u0026lt;/if_sid\u0026gt; \u0026lt;description\u0026gt;File berubah di direktori monitored: $(file)\u0026lt;/description\u0026gt; \u0026lt;/rule\u0026gt; \u0026lt;/group\u0026gt; Rule di atas akan terpicu kalau salah satu dari rule bawaan Wazuh, yaitu 550 (file berubah), 553 (file dihapus), atau 554 (file ditambahkan) tertrigger. Implementasi custom rule bisa jadi sangat luas. berikut:\nDetecting git pull \u0026lt;rule id=\u0026#34;100104\u0026#34; level=\u0026#34;10\u0026#34;\u0026gt; \u0026lt;if_sid\u0026gt;550,553,554\u0026lt;/if_sid\u0026gt; \u0026lt;field name=\u0026#34;file\u0026#34; type=\u0026#34;pcre2\u0026#34;\u0026gt;\\.git/FETCH_HEAD$\u0026lt;/field\u0026gt; \u0026lt;description\u0026gt;GIT Pull terdeteksi: $(file)\u0026lt;/description\u0026gt; \u0026lt;/rule\u0026gt; Detecting git clone \u0026lt;rule id=\u0026#34;100107\u0026#34; level=\u0026#34;10\u0026#34;\u0026gt; \u0026lt;if_sid\u0026gt;554\u0026lt;/if_sid\u0026gt; \u0026lt;field name=\u0026#34;file\u0026#34; type=\u0026#34;pcre2\u0026#34;\u0026gt;\\.git/refs/remotes/[^/]+/HEAD$\u0026lt;/field\u0026gt; \u0026lt;description\u0026gt;GIT Clone terdeteksi: $(file)\u0026lt;/description\u0026gt; \u0026lt;/rule\u0026gt; Detecting malicious file \u0026lt;rule id=\u0026#34;100108\u0026#34; level=\u0026#34;12\u0026#34;\u0026gt; \u0026lt;if_sid\u0026gt;554\u0026lt;/if_sid\u0026gt; \u0026lt;field name=\u0026#34;file\u0026#34; type=\u0026#34;pcre2\u0026#34;\u0026gt;\\.(sh|exe)(\\.\\w+)?$\u0026lt;/field\u0026gt; \u0026lt;description\u0026gt;File mencurigakan terdeteksi: $(file)\u0026lt;/description\u0026gt; \u0026lt;/rule\u0026gt; Kemudian, tambahkan element di dalam ossec.conf milik Wazuh manager. Jangan lupa untuk menyesuaikan name, hook url, dan api key-nya. Pastikan juga element group sudah berisi rules yang sebelumnya dibuat.\nnano config/wazuh_cluster/wazuh_manager.conf \u0026lt;ossec_config\u0026gt; \u0026lt;integration\u0026gt; \u0026lt;name\u0026gt;custom-brevo\u0026lt;/name\u0026gt; \u0026lt;hook_url\u0026gt;https://api.brevo.com/v3/smtp/email\u0026lt;/hook_url\u0026gt; \u0026lt;api_key\u0026gt;YOUR_BREVO_API_KEY\u0026lt;/api_key\u0026gt; \u0026lt;group\u0026gt;fim_email\u0026lt;/group\u0026gt; \u0026lt;alert_format\u0026gt;json\u0026lt;/alert_format\u0026gt; \u0026lt;/integration\u0026gt; ... \u0026lt;/ossec_config\u0026gt; Example output:\nMount file yang baru saja dibuat ke dalam volume Wazuh manager.\nnano docker-compose.yml services: wazuh.manager: ... volumes: ... - ./config/wazuh_cluster/wazuh_manager.conf:/wazuh-config-mount/etc/ossec.conf - ./config/wazuh_cluster/integrations/custom-brevo:/var/ossec/integrations/custom- brevo Example output:\nJangan lupa recreate Wazuh manager dengan cara docker compose down dan up ulang untuk mengapply konfigurasinya.\ndocker compose down \u0026amp;\u0026amp; docker compose up -d Di dalam wazuh manager, sesuaikan permission dan kepemilikan dari file-file yang baru saja ditambahkan.\ndocker exec single-node-wazuh.manager-1 chmod 750 /var/ossec/integrations/custom-brevo docker exec single-node-wazuh.manager-1 chown root:wazuh /var/ossec/integrations/custom-brevo Berikut adalah contoh dari email yang berhasil terkirim:\nKonfigurasi MySQL Monitoring + Email Alerting # MySQL Community Edition pada defaultnya tidak memiliki modul audit bawaan. Logging yang ditawarkan hanya berupa general log yang hanya mencatat setiap query yang dijalankan. Alternatifnya dapat menggunakan plugin audit dari Percona yang compatible dengan MySQL.\nInstalasi Percona pada MySQL non-docker # Pertama, cek dulu versi MySQL yang digunakan.\nmysql -u root -p SELECT VERSION(); exit Example output:\nDownload percona server sesuai dengan versi MySQL yang digunakan. Sesuaikan X dengan versi yang digunakan.\nwget https://downloads.percona.com/downloads/Percona-Server-X.X/Percona-Server-X.X.XX-XX/binary/tarball/Percona-Server-X.X.XX-XX-Linux.x86_64.glibc2.35-minimal.tar.gz Example output:\nEkstrak plugin audit dari archive yang telah didownlaod. Sesuaikan X dengan versi yang digunakan.\ntar -xzf Percona-Server-X.X.XX-XX-Linux.x86_64.glibc2.35-minimal.tar.gz \\ --wildcards \u0026#39;*/lib/plugin/audit_log.so\u0026#39; --strip-components=3 Example output:\nPindahkan file plugin ke dalam direktori plugin mysql.\nsudo cp audit_log.so /usr/lib/mysql/plugin/ # atau sudo cp audit_log.so /usr/lib64/mysql/plugin/ Example output:\nLakukan instalasi plugin tersebut beserta konfigurasinya ke dalam MySQL.\nmysql -u root -p INSTALL PLUGIN audit_log SONAME \u0026#39;audit_log.so\u0026#39;; exit Example output:\nVerifikasi apakah plugin auditnya sudah terpasang ke dalam MySQL.\nSHOW PLUGINS; Example output:\nTambahkan konfigurasi plugin audit pada file konfigurasi /etc/mysql/mysql.conf.d/mysqld.cnf\nsudo nano /etc/mysql/mysql.conf.d/mysqld.cnf Tambahkan konfigurasi berikut pada line paling bawah.\naudit-log=FORCE_PLUS_PERMANENT audit-log-format=JSON audit-log-file=/var/log/mysql/audit.log audit-log-policy=ALL Example output:\nRestart service mysql server\nsudo systemctl restart mysql.service Example output:\nInstalasi Percona pada MySQL docker # Pertama, cek dulu versi MySQL yang digunakan. Ganti NAMA_CONTAINER dan PASSWORD sesuai dengan konfigurasi container.\ndocker exec -it NAMA_CONTAINER mysql -u root -p PASSWORD -e \u0026#34;SELECT VERSION();\u0026#34; Expected output:\nDownload percona server sesuai dengan versi MySQL yang digunakan. Sesuaikan X dengan versi yang digunakan.\nwget https://downloads.percona.com/downloads/Percona-Server-X.X/Percona-Server-X.X.XX-XX/binary/tarball/Percona-Server-X.X.XX-XX-Linux.x86_64.glibc2.35-minimal.tar.gz Example output:\nEkstrak plugin audit dari archive yang telah didownlaod. Sesuaikan X dengan versi yang digunakan.\ntar -xzf Percona-Server-X.X.XX-XX-Linux.x86_64.glibc2.35-minimal.tar.gz \\ --wildcards \u0026#39;*/lib/plugin/audit_log.so\u0026#39; --strip-components=3 Example output:\nBerikan plugin tersebut beserta konfigurasinya ke dalam MySQL container. Pada kasus ini menggunakan Docker compose.\nnano docker-compose.yml Recreate database menjalankan perintah docker compose up -d ulang.\ndocker compose up -d Expected output:\nLakukan instalasi terhadap plugin audit di dalam container. Ganti NAMA_CONTAINER dan PASSWORD sesuai dengan konfigurasi container.\ndocker exec -it NAMA_CONTAINER mysql -u root -p PASSWORD -e \u0026#34;INSTALL PLUGIN audit_log SONAME \u0026#39;audit_log.so\u0026#39;;\u0026#34; Verifikasi apakah plugin auditnya sudah terpasang ke dalam MySQL.\ndocker exec -it NAMA_CONTAINER mysql -u root -p PASSWORD -e \u0026#34;SHOW PLUGINS;\u0026#34; | grep audit Expected output:\nKonfigurasi MySQL Custom Rules # Pada menu Server Management \u0026gt; Rules, buat custom rules untuk menginterpretasikan log dari MySQL ke dalam sistem alert. Reload konfigurasi pada pop-up notifikasi yang muncul.\nContoh rules MySQL monitoring:\n\u0026lt;group name=\u0026#34;mysql\u0026#34;\u0026gt; \u0026lt;rule id=\u0026#34;100172\u0026#34; level=\u0026#34;7\u0026#34;\u0026gt; \u0026lt;decoded_as\u0026gt;json\u0026lt;/decoded_as\u0026gt; \u0026lt;field name=\u0026#34;audit_record.sqltext\u0026#34; type=\u0026#34;pcre2\u0026#34;\u0026gt;(?i)^drop\\s+user\u0026lt;/field\u0026gt; \u0026lt;description\u0026gt;MySQL DROP USER detected\u0026lt;/description\u0026gt; \u0026lt;mitre\u0026gt; \u0026lt;id\u0026gt;T1078\u0026lt;/id\u0026gt; \u0026lt;/mitre\u0026gt; \u0026lt;/rule\u0026gt; \u0026lt;rule id=\u0026#34;100156\u0026#34; level=\u0026#34;12\u0026#34;\u0026gt; \u0026lt;decoded_as\u0026gt;json\u0026lt;/decoded_as\u0026gt; \u0026lt;field name=\u0026#34;audit_record.sqltext\u0026#34; type=\u0026#34;pcre2\u0026#34;\u0026gt;(?i)^delete\\s+from\\s+\u0026lt;/field\u0026gt; \u0026lt;description\u0026gt;MySQL DML DELETE detected\u0026lt;/description\u0026gt; \u0026lt;mitre\u0026gt; \u0026lt;id\u0026gt;T1485\u0026lt;/id\u0026gt; \u0026lt;/mitre\u0026gt; \u0026lt;/rule\u0026gt; \u0026lt;rule id=\u0026#34;100180\u0026#34; level=\u0026#34;10\u0026#34;\u0026gt; \u0026lt;decoded_as\u0026gt;json\u0026lt;/decoded_as\u0026gt; \u0026lt;field name=\u0026#34;audit_record.sqltext\u0026#34; type=\u0026#34;pcre2\u0026#34;\u0026gt;(?i)^load\\s+data\\s+(local\\s+)?infile\u0026lt;/field\u0026gt; \u0026lt;description\u0026gt;MySQL DML LOAD DATA INFILE detected\u0026lt;/description\u0026gt; \u0026lt;mitre\u0026gt; \u0026lt;id\u0026gt;T1059\u0026lt;/id\u0026gt; \u0026lt;/mitre\u0026gt; \u0026lt;/rule\u0026gt; \u0026lt;rule id=\u0026#34;100152\u0026#34; level=\u0026#34;12\u0026#34;\u0026gt; \u0026lt;decoded_as\u0026gt;json\u0026lt;/decoded_as\u0026gt; \u0026lt;field name=\u0026#34;audit_record.sqltext\u0026#34; type=\u0026#34;pcre2\u0026#34;\u0026gt;(?i)^drop\\s+table\u0026lt;/field\u0026gt; \u0026lt;description\u0026gt;MySQL DROP TABLE detected\u0026lt;/description\u0026gt; \u0026lt;mitre\u0026gt; \u0026lt;id\u0026gt;T1485\u0026lt;/id\u0026gt; \u0026lt;/mitre\u0026gt; \u0026lt;/rule\u0026gt; \u0026lt;rule id=\u0026#34;100155\u0026#34; level=\u0026#34;12\u0026#34;\u0026gt; \u0026lt;decoded_as\u0026gt;json\u0026lt;/decoded_as\u0026gt; \u0026lt;field name=\u0026#34;audit_record.sqltext\u0026#34; type=\u0026#34;pcre2\u0026#34;\u0026gt;(?i)^drop\\s+database\u0026lt;/field\u0026gt; \u0026lt;description\u0026gt;MySQL DROP DATABASE detected\u0026lt;/description\u0026gt; \u0026lt;mitre\u0026gt; \u0026lt;id\u0026gt;T1485\u0026lt;/id\u0026gt; \u0026lt;/mitre\u0026gt; \u0026lt;/rule\u0026gt; \u0026lt;rule id=\u0026#34;100174\u0026#34; level=\u0026#34;10\u0026#34;\u0026gt; \u0026lt;decoded_as\u0026gt;json\u0026lt;/decoded_as\u0026gt; \u0026lt;field name=\u0026#34;audit_record.sqltext\u0026#34; type=\u0026#34;pcre2\u0026#34;\u0026gt;(?i)^truncate(\\s+table)?\\s+\u0026lt;/field\u0026gt; \u0026lt;description\u0026gt;MySQL TRUNCATE TABLE detected\u0026lt;/description\u0026gt; \u0026lt;mitre\u0026gt; \u0026lt;id\u0026gt;T1485\u0026lt;/id\u0026gt; \u0026lt;/mitre\u0026gt; \u0026lt;/rule\u0026gt; \u0026lt;rule id=\u0026#34;100181\u0026#34; level=\u0026#34;12\u0026#34;\u0026gt; \u0026lt;decoded_as\u0026gt;json\u0026lt;/decoded_as\u0026gt; \u0026lt;field name=\u0026#34;audit_record.sqltext\u0026#34; type=\u0026#34;pcre2\u0026#34;\u0026gt;(?i)into\\s+outfile\\s+\u0026lt;/field\u0026gt; \u0026lt;description\u0026gt;MySQL SELECT INTO OUTFILE detected\u0026lt;/description\u0026gt; \u0026lt;mitre\u0026gt; \u0026lt;id\u0026gt;T1048\u0026lt;/id\u0026gt; \u0026lt;/mitre\u0026gt; \u0026lt;/rule\u0026gt; \u0026lt;rule id=\u0026#34;100182\u0026#34; level=\u0026#34;14\u0026#34;\u0026gt; \u0026lt;decoded_as\u0026gt;json\u0026lt;/decoded_as\u0026gt; \u0026lt;field name=\u0026#34;audit_record.sqltext\u0026#34; type=\u0026#34;pcre2\u0026#34;\u0026gt;(?i)into\\s+dumpfile\\s+\u0026lt;/field\u0026gt; \u0026lt;description\u0026gt;MySQL SELECT INTO DUMPFILE detected\u0026lt;/description\u0026gt; \u0026lt;mitre\u0026gt; \u0026lt;id\u0026gt;T1048\u0026lt;/id\u0026gt; \u0026lt;/mitre\u0026gt; \u0026lt;/rule\u0026gt; \u0026lt;/group\u0026gt; Rules di atas pada dasarnya menyaring json dari plugin audit Percona yang dikirimkan oleh Wazuh agent. Proses penyaringannya menggunakan filter Regex pada element . Berikut adalah contoh untuk menyaring event revoke pada database:\n\u0026lt;rule id=\u0026#34;100199\u0026#34; level=\u0026#34;7\u0026#34;\u0026gt; \u0026lt;decoded_as\u0026gt;json\u0026lt;/decoded_as\u0026gt; \u0026lt;field name=\u0026#34;audit_record.sqltext\u0026#34; type=\u0026#34;pcre2\u0026#34;\u0026gt;(?i)^revoke\\s+\u0026lt;/field\u0026gt; \u0026lt;description\u0026gt;MySQL REVOKE detected\u0026lt;/description\u0026gt; \u0026lt;/rule\u0026gt; Selanjutnya, pada server agent, tambahkan konfigurasi di /var/ossec/etc/ossec.conf untuk mengambil log dari database MySQL.\nsudo nano /var/ossec/etc/ossec.conf \u0026lt;localfile\u0026gt; \u0026lt;log_format\u0026gt;syslog\u0026lt;/log_format\u0026gt; \u0026lt;location\u0026gt;/path/to/audit.log\u0026lt;/location\u0026gt; \u0026lt;/localfile\u0026gt; Restart service wazuh agent.\nsudo systemctl restart wazuh-agent Example output:\nPengujian membuat dan menghapus tabel baru di database untuk memastikan decoder dan rulesnya berjalan dengan lancar.\nCREATE TABLE testing (id INT); DROP TABLE testing; Example output:\nKonfigurasi Email Alerting untuk MySQL Event # Terlihat di atas bahwa eventnya sudah masuk ke dalam alert. Selanjutnya adalah konfigurasi email alerting yang dihubungkan.\nBuat custom python script baru bernama custom-brevo-mysql untuk memparsing dan mengirim email ke API Brevo. Script untuk custom external API integration harus dimulai dengan prefic custom-. Letakkan scriptnya di folder wazuh-docker/single-node/config/wazuh_cluster/integrations/\ncd wazuh-docker/single-node nano config/wazuh_cluster/integrations/custom-brevo-mysql Berikut contoh custom scriptnya. Sesuaikan EMAIL_BREVO, EMAIL_TUJUAN, dan NAMA_PENERIMA.\n#!/usr/bin/env python3 import json import sys import time from urllib.request import Request, urlopen from urllib.error import HTTPError, URLError SENDER_EMAIL = \u0026#34;EMAIL_BREVO\u0026#34; SENDER_NAME = \u0026#34;Wazuh Alert\u0026#34; RECIPIENT_LIST = [ {\u0026#34;email\u0026#34;: \u0026#34;EMAIL_TUJUAN1\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;NAMA_PENERIMA\u0026#34;}, {\u0026#34;email\u0026#34;: \u0026#34;EMAIL_TUJUAN2\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;NAMA_PENERIMA\u0026#34;}, ] SUBJECT_PREFIX = \u0026#34;[Wazuh]\u0026#34; LOG_FILE = \u0026#34;/var/ossec/logs/integrations.log\u0026#34; def log(msg): timestamp = time.strftime(\u0026#34;%Y-%m-%d %H:%M:%S\u0026#34;, time.localtime()) entry = \u0026#34;{} custom-brevo-mysql: {}\\n\u0026#34;.format(timestamp, msg) try: with open(LOG_FILE, \u0026#34;a\u0026#34;) as f: f.write(entry) except Exception: pass def read_alert(alert_file_path): try: with open(alert_file_path) as f: alert = json.load(f) return alert except Exception as e: log(\u0026#34;ERROR: Gagal membaca alert file {}: {}\u0026#34;.format(alert_file_path, e)) sys.exit(1) def build_subject(alert): rule = alert.get(\u0026#34;rule\u0026#34;, {}) rule_level = rule.get(\u0026#34;level\u0026#34;, 0) description = rule.get(\u0026#34;description\u0026#34;, \u0026#34;Wazuh Alert\u0026#34;) if rule_level \u0026gt;= 12: severity = \u0026#34;CRITICAL\u0026#34; elif rule_level \u0026gt;= 8: severity = \u0026#34;HIGH\u0026#34; elif rule_level \u0026gt;= 5: severity = \u0026#34;MEDIUM\u0026#34; else: severity = \u0026#34;LOW\u0026#34; return \u0026#34;{} [{}] {}\u0026#34;.format(SUBJECT_PREFIX, severity, description) def build_html(alert): rule = alert.get(\u0026#34;rule\u0026#34;, {}) agent = alert.get(\u0026#34;agent\u0026#34;, {}) data = alert.get(\u0026#34;data\u0026#34;, {}) audit = data.get(\u0026#34;audit_record\u0026#34;, {}) timestamp = alert.get(\u0026#34;timestamp\u0026#34;, \u0026#34;-\u0026#34;) rule_level = rule.get(\u0026#34;level\u0026#34;, 0) rule_desc = rule.get(\u0026#34;description\u0026#34;, \u0026#34;-\u0026#34;) agent_name = agent.get(\u0026#34;name\u0026#34;, \u0026#34;-\u0026#34;) agent_ip = agent.get(\u0026#34;ip\u0026#34;, \u0026#34;-\u0026#34;) sqltext = audit.get(\u0026#34;sqltext\u0026#34;, \u0026#34;-\u0026#34;) mysql_user = audit.get(\u0026#34;user\u0026#34;, \u0026#34;-\u0026#34;) mysql_host = audit.get(\u0026#34;host\u0026#34;, \u0026#34;-\u0026#34;) mysql_ip = audit.get(\u0026#34;ip\u0026#34;, \u0026#34;-\u0026#34;) mysql_db = audit.get(\u0026#34;db\u0026#34;, \u0026#34;-\u0026#34;) if rule_level \u0026gt;= 12: color = \u0026#34;#c0392b\u0026#34; elif rule_level \u0026gt;= 8: color = \u0026#34;#d35400\u0026#34; elif rule_level \u0026gt;= 5: color = \u0026#34;#e67e22\u0026#34; else: color = \u0026#34;#2980b9\u0026#34; ROW = \u0026#39;\u0026lt;tr{bg}\u0026gt;\u0026lt;td style=\u0026#34;padding:6px 12px;font-weight:bold;width:130px;color:#555;\u0026#34;\u0026gt;{label}\u0026lt;/td\u0026gt;\u0026lt;td style=\u0026#34;padding:6px 12px;\u0026#34;\u0026gt;{value}\u0026lt;/td\u0026gt;\u0026lt;/tr\u0026gt;\u0026#39; ZEBRA = \u0026#39; style=\u0026#34;background:#f9f9f9;\u0026#34;\u0026#39; SEPARATOR = \u0026#39;\u0026lt;tr\u0026gt;\u0026lt;td colspan=\u0026#34;2\u0026#34; style=\u0026#34;border-top:1px solid #eee;padding:0;\u0026#34;\u0026gt;\u0026lt;/td\u0026gt;\u0026lt;/tr\u0026gt;\u0026#39; rows = [] rows.append(ROW.format(bg=\u0026#34;\u0026#34;, label=\u0026#34;Query\u0026#34;, value=\u0026#34;\u0026lt;code\u0026gt;{}\u0026lt;/code\u0026gt;\u0026#34;.format(sqltext))) rows.append(SEPARATOR) rows.append(ROW.format(bg=\u0026#34;\u0026#34;, label=\u0026#34;MySQL User\u0026#34;, value=mysql_user)) rows.append(ROW.format(bg=ZEBRA, label=\u0026#34;Host\u0026#34;, value=mysql_host)) rows.append(ROW.format(bg=\u0026#34;\u0026#34;, label=\u0026#34;IP\u0026#34;, value=mysql_ip if mysql_ip else \u0026#34;-\u0026#34;)) rows.append(ROW.format(bg=ZEBRA, label=\u0026#34;Database\u0026#34;, value=mysql_db)) rows.append(SEPARATOR) rows.append(ROW.format(bg=\u0026#34;\u0026#34;, label=\u0026#34;Agent\u0026#34;, value=\u0026#34;{} ({})\u0026#34;.format(agent_name, agent_ip))) rows.append(ROW.format(bg=ZEBRA, label=\u0026#34;Time\u0026#34;, value=timestamp)) table_rows = \u0026#34;\\n \u0026#34;.join(rows) html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;meta charset=\u0026#34;utf-8\u0026#34;\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body style=\u0026#34;font-family:Arial,Helvetica,sans-serif;margin:0;padding:0;background:#f4f4f4;\u0026#34;\u0026gt; \u0026lt;div style=\u0026#34;max-width:600px;margin:20px auto;background:#fff;border-radius:8px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.1);\u0026#34;\u0026gt; \u0026lt;div style=\u0026#34;background:{color};color:#fff;padding:14px 20px;\u0026#34;\u0026gt; \u0026lt;p style=\u0026#34;margin:0;font-size:16px;font-weight:bold;\u0026#34;\u0026gt;{desc}\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;table style=\u0026#34;width:100%;border-collapse:collapse;font-size:14px;\u0026#34;\u0026gt; {table_rows} \u0026lt;/table\u0026gt; \u0026lt;div style=\u0026#34;background:#ecf0f1;padding:8px 20px;font-size:11px;color:#95a5a6;text-align:center;\u0026#34;\u0026gt; Wazuh MySQL Alert \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34;.format( color=color, desc=rule_desc, table_rows=table_rows, ) return html def send_email(api_key, hook_url, subject, html_content): payload = { \u0026#34;sender\u0026#34;: { \u0026#34;name\u0026#34;: SENDER_NAME, \u0026#34;email\u0026#34;: SENDER_EMAIL, }, \u0026#34;to\u0026#34;: RECIPIENT_LIST, \u0026#34;subject\u0026#34;: subject, \u0026#34;htmlContent\u0026#34;: html_content, } data = json.dumps(payload).encode(\u0026#34;utf-8\u0026#34;) req = Request(hook_url, data=data) req.add_header(\u0026#34;accept\u0026#34;, \u0026#34;application/json\u0026#34;) req.add_header(\u0026#34;content-type\u0026#34;, \u0026#34;application/json\u0026#34;) req.add_header(\u0026#34;api-key\u0026#34;, api_key) try: response = urlopen(req) response_body = response.read().decode(\u0026#34;utf-8\u0026#34;) log(\u0026#34;OK: Email terkirim. Response: {}\u0026#34;.format(response_body)) except HTTPError as e: error_body = e.read().decode(\u0026#34;utf-8\u0026#34;) if hasattr(e, \u0026#34;read\u0026#34;) else str(e) log(\u0026#34;ERROR: HTTP {} - {}\u0026#34;.format(e.code, error_body)) sys.exit(1) except URLError as e: log(\u0026#34;ERROR: Koneksi gagal - {}\u0026#34;.format(e.reason)) sys.exit(1) except Exception as e: log(\u0026#34;ERROR: UnExample - {}\u0026#34;.format(e)) sys.exit(1) def main(): if len(sys.argv) \u0026lt; 4: log(\u0026#34;ERROR: Argumen tidak lengkap. Usage: custom-brevo-mysql \u0026lt;alert_file\u0026gt; \u0026lt;api_key\u0026gt; \u0026lt;hook_url\u0026gt;\u0026#34;) sys.exit(1) alert_file_path = sys.argv[1] api_key = sys.argv[2] hook_url = sys.argv[3] alert = read_alert(alert_file_path) subject = build_subject(alert) html = build_html(alert) log(\u0026#34;Mengirim email untuk rule {} (level {})...\u0026#34;.format( alert.get(\u0026#34;rule\u0026#34;, {}).get(\u0026#34;id\u0026#34;, \u0026#34;?\u0026#34;), alert.get(\u0026#34;rule\u0026#34;, {}).get(\u0026#34;level\u0026#34;, \u0026#34;?\u0026#34;), )) send_email(api_key, hook_url, subject, html) if __name__ == \u0026#34;__main__\u0026#34;: main() Script di atas betrujuan untuk membaca file JSON alert dari rule yang tertrigger, kemudian membuat subject email berdasarkan severity (LOW/MEDIUM/HIGH/CRITICAL). Isi dari emailnya berupa HTML yang berisi detail perubahan file, yaitu path, event, diff, user, process, info agent. Email akan dikirim ke daftar penerima yang tertera.\nKemudian, tambahkan element di dalam ossec.conf milik Wazuh manager. Jangan lupa untuk menyesuaikan name, hook url, dan api key-nya. Pastikan juga element group sudah merujuk ke rules yang sebelumnya dibuat.\nnano config/wazuh_cluster/wazuh_manager.conf \u0026lt;ossec_config\u0026gt; \u0026lt;integration\u0026gt; \u0026lt;name\u0026gt;custom-brevo-mysql\u0026lt;/name\u0026gt; \u0026lt;hook_url\u0026gt;https://api.brevo.com/v3/smtp/email\u0026lt;/hook_url\u0026gt; \u0026lt;api_key\u0026gt;YOUR_BREVO_API_KEY\u0026lt;/api_key\u0026gt; \u0026lt;group\u0026gt;mysql\u0026lt;/group\u0026gt; \u0026lt;alert_format\u0026gt;json\u0026lt;/alert_format\u0026gt; \u0026lt;/integration\u0026gt; ... \u0026lt;/ossec_config\u0026gt; Example output:\nMount file yang baru saja dibuat ke dalam volume Wazuh manager.\nnano docker-compose.yml services: wazuh.manager: ... volumes: ... - ./config/wazuh_cluster/wazuh_manager.conf:/wazuh-config-mount/etc/ossec.conf - ./config/wazuh_cluster/integrations/custom-brevo-mysql:/var/ossec/integrations/c ustom-brevo-mysql Example output:\nJangan lupa recreate Wazuh manager dengan cara docker compose down dan up ulang untuk mengapply konfigurasinya.\ndocker compose down \u0026amp;\u0026amp; docker compose up -d Di dalam wazuh manager, sesuaikan permission dan kepemilikan dari file-file yang baru saja ditambahkan.\ndocker exec single-node-wazuh.manager-1 chmod 750 /var/ossec/integrations/custom-brevo-mysql docker exec single-node-wazuh.manager-1 chown root:wazuh /var/ossec/integrations/custom-brevo-mysql Berikut adalah contoh dari email yang berhasil terkirim:\n","date":"22 Oktober 2025","externalUrl":null,"permalink":"/posts/wazuh/","section":"Semua Artikel","summary":"Wazuh adalah platform security open-source yang memiliki kapabilitas sebagai Security Information and Event Management (SIEM).\nSebagai SIEM, Wazuh bisa menjadi tempat penyimpanan dan pengolahan log yang tersentralisasi pada sistem SMSCrops, baik itu log dari sisi server maupun database. Log tersebut menyimpan informasi yang dianalisa Wazuh secara realtime untuk mendeteksi adanya security event.\n","title":"Panduan Implementasi Wazuh sebagai Solusi SIEM Open-Source","type":"posts"},{"content":"","date":"22 Oktober 2025","externalUrl":null,"permalink":"/tags/security/","section":"Tags","summary":"","title":"Security","type":"tags"},{"content":"","date":"22 Oktober 2025","externalUrl":null,"permalink":"/tags/siem/","section":"Tags","summary":"","title":"SIEM","type":"tags"},{"content":"","date":"22 Oktober 2025","externalUrl":null,"permalink":"/tags/wazuh/","section":"Tags","summary":"","title":"Wazuh","type":"tags"},{"content":"","date":"29 September 2025","externalUrl":null,"permalink":"/tags/app-runner/","section":"Tags","summary":"","title":"App Runner","type":"tags"},{"content":"","date":"29 September 2025","externalUrl":null,"permalink":"/tags/serverless/","section":"Tags","summary":"","title":"Serverless","type":"tags"},{"content":"Terkadang, aplikasi yang seharusnya dideploy dengan infrastruktur yang simple, malah didapati dengan over-engineering. Tidak semua aplikasi, khususnya container, butuh cluster Kubernetes yang kompleks. Beberapa hanya butuh infrastruktur yang simple namun scalable. Bagaimana ya, implementasinya di AWS?\nArtikel ini adalah rangkuman dari presentasi POROS Filkom Study Group 2025. Berikut slide presentasi yang digunakan:\nPrevious Next Pendahuluan # Serverless Container, apa sih? # Singkat saja, Serverless container adalah pendekatan untuk menjalankan container di cloud tanpa perlu mengelola infrastruktur servernya. Dengan begitu, developer jadi bisa fokus ke aplikasi, bukan ke infranya.\nMasalahnya begini, # Bayangkan, client menginfokan kalau aplikasi harus live besok pagi jam 6. Apabila tidak ada tim Ops, artinya, tim developer harus handle deploymentnya sendiri.\nUntuk mendeploy production-ready application secara tradisional, setidaknya ada beberapa hal yang harus disiapkan:\nBase infrastructure, yaitu server beserta ekosistemnya Auto scaling load balancing Networking, subnet, firewall Domain + SSL Dan masih banyak lagi. Kalaulah memang harus diproses sendiri pasti memakan waktu dan membutuhkan skill infrastruktur yang tidak selalu dimiliki tim devloper. Maka dari itu, disinilah konsep Container as a Service dapat menjadi solusi yang menyederhanakan semuanya.\nSekilas tentang Cloud, just in case you\u0026rsquo;re not familiar, yet. # Cloud computing adalah terminlogi penggunaan infrastruktur remote yang dihost di internet untuk mengolah data, berbeda dengan menggunakan server lokal. Singkatnya, cloud menyediakan on-demand delivery untuk resource IT melalui internet dengan model pembayaran pay-as-you-go. Daripada membeli dan merawat data center sendiri, lebih baik menyewa layanan yang bisa diakses sesuai kebutuhan dari cloud provider.\nSedikit intermezzo tentang cloud # Istilah cloud sendiri sebenarnya hanyalah cara simple untuk menggambarkan sesuatu yang terlalu kompleks untuk digambar. Dulu, topologi internet hanya sesimple beberapa perangkat yang dapat digambarkan secara detail, seperti ini:\nNamun, seiring berkembangnya internet, topologi internet semakin rumit untuk digambarkan.\nShingga, Kata cloud sebenarnya digunakan untuk mendeskripsikan kumpulan objek yang secara visual tampak seperti awan dari jauh.\nJadi, terminologi cloud adalah terjemahan untuk, \u0026ldquo;Bang, ini tuh lebih ribet dari yang lu ngerti, dan kita juga males ngegambarinnya, repot. Jadi anggap aja ini saking banyaknya tuh kayak kumpulan awan di sana, adalah pokoknya.\u0026rdquo;\nContainer di Cloud # Cloud container pada dasarnya adalah container biasa yang berjalan di infrastruktur cloud, bukan di mesin lokal atau data center sendiri.\nDulu, sebelum era infrastruktur cloud, trennya adalah menggunakan infrastuktur on-premise untuk mendeploy aplikasi yang masih bersifat monolith. Seiring berkembangnya zaman, tren aplikasi berubah dengan microservice yang terintegrasi dengan ekosistem cloud. Biasanya, aplikasi tersebut dideploy dalam bentuk container.\nServerless Container # Serverless adalah kategori layanan cloud di mana pengguna bisa memanfaatkan berbagai kapabilitas cloud tanpa harus melakukan provisioning, deploy, dan mengelola hardware maupun software resource. Istilah serverless container merepresentasikan ide bahwa pengguna sekarang bisa menjalankan container tanpa harus mengelola server atau infrastruktur komputasi yang menjalankan container tersebut.\nServerless itu artinya tidak perlu merawat infrastruktur. Serverless itu artinya service akan melakukan scale berdasarkan request secara realtime Serverless itu artinya bisa membayar hanya sesuai pemakaian dan durasi penggunaan\nDari FaaS ke CaaS # Secara tradisional, istilah serverless itu diartikan dengan model Functions as a Service (FaaS), yaitu model eksekusi cloud computing yang hany memberikan resource komputasi berdasarkan pemakaian. Sehingga, pengguna tidak perlu berurusan dengan maintenance infrastruktur.\nSeiring waktu, pengguna ingin memperluas pendekatan tersebut. Daripada mengemas kode dalam file ZIP dan mengirimnya ke platform serverless, mereka ingin cara untuk mengirim container langsung. Belakangan ini, hal tersebut sudah tersedia dengan teknologi Containers as a Service (CaaS) yang sekarang dikenal sebagai serverless container.\nKenapa harus Amazon Web Services? # Amazon Web Services diluncurkan pada tahun 2006 dan awalnya dirancang untuk menyediakan infrastruktur untuk ritel Amazon sendiri. Saat ini, AWS adalah cloud provider terbesar di dunia dengan market share sekitar 33%. Beberapa perusahaan besar dunia seperti Netflix, Airbnb, dan Snapchat menjalankan bisnis mereka di AWS.\nHampir 80 persen dari seluruh cloud container berjalan di Amazon Web Services.1\nServerless Container di AWS # ECS (Elastic Container Service) # Amazon Elastic Container Service (ECS) adalah fully managed container orchestration service. Fungsinya mirip dengan Kubernetes dan Docker Swarm, tapi ECS dimiliki oleh AWS dan tidak perlu disetup sendiri.\nBahkan kedua teknologi ini memiliki terminologi yang sebenarnya sama saja.\nEKS (Elastic Kubernetes Service) # Amazon Elastic Kubernetes Service (EKS) menyediakan fully managed Kubernetes service yang menghilangkan kompleksitas dalam mengoperasikan cluster Kubernetes.\nApp Runner # AWS App Runner adalah layanan AWS yang menyediakan cara cepat, sederhana, dan hemat biaya untuk mendeploy aplikasi web yang scalable dan secure langsung dari source code atau container image ke AWS Cloud. Tidak perlu mempelajari teknologi baru, memilih compute service mana yang digunakan, atau mengetahui cara melakukan provisioning dan konfigurasi resource AWS.\nApp Runner terhubung langsung ke repositori kode atau image. App Runner menyediakan automatic integration and delivery pipeline dengan operasi yang fully managed, performa tinggi, scalability, dan keamanan.\nUntuk developer, App Runner menyederhanakan proses deployment versi baru dari kode atau image repository. Untuk tim operasi, App Runner mengaktifkan automatic deployment setiap kali commit dipush ke repositori kode atau versi container image baru dipush ke image repository.\nHanya saja, sayangnya, App Runner tidak tersedia di seluruh region. Saat ini, hanya beberapa region saja yang support App Runner, seperti yang dapat dilihat pada tabel di atas.\nAWS Lambda # AWS Lambda adalah compute service yang menjalankan kode tanpa perlu mengelola server. Kode berjalan dengan scaling otomatis naik dan turun, dengan harga pay-per-use. Lambda menjalankan kode di infrastruktur komputasi high-availability milik AWS.\nKarena Lambda adalah serverless, event-driven compute service, Lambda menggunakan paradigma pemrograman yang berbeda dari aplikasi web tradisional.\nPerlu diperhatikan bahwa AWS Lambda memiliki beberapa limitasi. Salah satu yang paling noticable adalah batas 6MB yang berlaku untuk request dan response payload. Hal ini bisa menjadi masalah jika memiliki HTTP API yang memperbolehkan pengguna meng-upload gambar dan file ke S3.\nJadi, pake apa? # Begini perbandingan keempat layanan serverless container di AWS yang sudah kita bahas tadi:\nLambda cocok untuk short-running tasks dan cron jobs, tapi dibatasi maksimal 15 menit runtime.\nApp Runner paling simple untuk aplikasi request response sederhana. Memang tidak menyediakan opsi konfigurasi yang terlalu detail, tapi cukup untuk kebanyakan aplikasi.\nECS Fargate itu agak kompleks, tapi menawarkan fleksibilitas dan kontrol konfigurasi yang lebih luas daripada App Runner.\nEKS Fargate cocok untuk workload yang sangat besar dengan ekosistem Kubernetes yang luas, tapi tradeoffnya adalah, kompleksitasnya paling tinggi.\nJadi sebenarnya, AWS menyediakan beberapa opsi layanan container yang tersusun dalam beberapa layer.\nDi layer provisioning ada AWS App Runner, AWS Elastic Beanstalk, dan Amazon Lightsail. Fokus mereka adalah menyederhanakan proses deployment tanpa perlu mengurus infrastruktur. Lanjut ke layer orchestration ada Amazon ECS, Amazon EKS, dan ROSA yang fokus ke orkestrasi container. Di paling bawah ada layer capacity, terdiri dari Amazon EC2 dan AWS Fargate. Pada dasarnya, keduanya adalah computing resource untuk menjalankan semua layanan container tersebut.\nSemakin ke atas layernya, semakin sedikit yang perlu dikelola. Semakin ke bawah, semakin banyak kontrol yang didapat tapi juga semakin banyak yang harus dimanage sendiri.\nStudi Kasus di Campushub Universitas Brawijaya # Kita ambil satu contoh yaitu Campushub, salah satu produk startup internal yang diciptakan mahasiswa Universitas Brawijaya. Dalam pengembangannya, aplikasi ini cukup simple, hanya terdiri dari frontend React dan backend Laravel. Kira-kira, apa solusi scalable deployment yang cocok untuk productionnya dari mulai skala kecil?\nMurah gak, sih? # Karena masih dalam production skala kecil, Campushub hanya butuh spesifikasi kecil, sekitar 0.5 vCPU dan 1GB RAM per service. Perlu diingat juga, Campushub butuh autoscaling untuk antisipasi lonjakan traffic setelah grand launching.\nBerikut perbandingan biaya bulanan untuk menjalankan 1 service di layanan container yang telah kita bahas, pada region singapore.\nLayanan vCPU RAM Harga vCPU Harga RAM Biaya Lain Total/Bulan App Runner 0.5 1GB $0.064/vCPU-hr (active) $0.007/GB-hr - $14.71 ECS Fargate 0.5 1GB $0.05056/vCPU-hr $0.00553/GB-hr - $22.49 EKS EC2 2 (t4g.micro) 1GB $0.0106/hr (instance) included $73 (cluster) $80.74 EC2 standalone 2 (t4g.micro) 1GB $0.0106/hr (instance) included - $7.74 Rincian kalkulasinya sebagai berikut.\nApp Runner mengenakan $0.064/vCPU-hour untuk *active instance* dan $0.007/GB-hour untuk memori.2 Yang menarik, ketika traffic sedang kosong, instance akan masuk ke mode idle dan hanya dikenakan biaya memori tanpa biaya vCPU. Dengan asumsi hanya active sekitar 300 jam per bulan, maka:\nvCPU (aktif 300 jam): 0.5 x $0.064 x 300 = $9.60 RAM (730 jam penuh, termasuk idle): 1 x $0.007 x 730 = $5.11 Total per service: $14.71/bulan ECS Fargate mengenakan $0.05056/vCPU-hour dan $0.00553/GB-hour di ap-southeast-1 (Linux/x86).3 Fargate tidak punya mekanisme idle seperti App Runner. Selama task berjalan, biayanya tetap sama terlepas dari ada traffic atau tidak. Untuk 0.5 vCPU dan 1GB RAM yang berjalan 24/7 selama 730 jam:\nvCPU: 0.5 x $0.05056 x 730 = $18.45 RAM: 1 x $0.00553 x 730 = $4.04 Total per service: $22.49/bulan EKS EC2 dan EC2 standalone menggunakan instance t4g.micro (2vCPU 1GB RAM) seharga $0.0106/jam di ap-southeast-1. Sehingga, biayanya dihitung per instance, bukan per container:\nBiaya instance: $0.0106 x 730 = $7.74/bulan Sehingga untuk 2 service, totalnya sekitar $29.42/bulan dengan App Runner. Nominal tersebut jauh lebih rendah dibandingkan ECS Fargate dan EKS EC2.\nMemang, EC2 standalone lebih murah. Apalagi kedua service bisa dijalankan di instance yang sama, sehingga totalnya tetap $7.74/bulan. Tapi tidak ada *autoscaling*, tetap di*charge* penuh walaupun *traffic* kosong, dan harus memaintenance segalanya sendiri. Untuk EKS EC2, ditambah biaya *cluster* EKS $0.10/jam menjadi $80.74/bulan.4\nMaka dari itu, untuk Campushub yang masih dalam tahap production berskala kecil dengan traffic ringan, App Runner menjadi solusi managed service yang paling cocok. Harganya affordable, simple, scalable, dan reliable.\nMari Implementasi! # Campushub terdiri dari dua komponen, yaitu frontend React dan backend Laravel. Keduanya dicontainerize menggunakan Docker dan dideploy ke App Runner melalui ECR.\nPush Image ke ECR # Pertama, mari kita buat registry ECR terlebih dahulu untuk container image Campushub. Cari layanan ECR melalui pencarian di console.\nKlik tombol create pada bagian create repository.\nBerikan nama untuk repository yang akan dibuat untuk kedua image, yaitu frontend dan backend. Biarkan value yang lainnya default saua. Lalu, klik create pada bagian bawah.\nJika berhasil, nantinya akan muncul kedua repository sebagai berikut.\nKemudian, push image Campushub ke ECR tadi. Bisa menggunakan AWS CLI yang sudah terpasang pada server yang memiliki imagenya. Jika belum terpasang, baca panduannya di sini.\naws login --remote aws ecr get-login-password --region ap-southeast-1 | docker login --username AWS --password-stdin 768052528660.dkr.ecr.ap-southeast-1.amazonaws.com docker tag campushub-backend:latest 768052528660.dkr.ecr.ap-southeast-1.amazonaws.com/campushub/backend:latest docker push 768052528660.dkr.ecr.ap-southeast-1.amazonaws.com/campushub/backend:latest docker tag campushub-frontend:latest 768052528660.dkr.ecr.ap-southeast-1.amazonaws.com/campushub/frontend:latest docker push 768052528660.dkr.ecr.ap-southeast-1.amazonaws.com/campushub/frontend:latest Membuat IAM Role untuk App Runner # Sebelum membuat App Runner service, diperlukan sebuah IAM role yang mengizinkan App Runner untuk menarik image dari ECR. Tanpa role ini, App Runner tidak punya akses ke private repository ECR.\naws iam create-role \\ --role-name CampushubAppRunnerECRAccessRole \\ --assume-role-policy-document \u0026#39;{ \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [ { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Principal\u0026#34;: { \u0026#34;Service\u0026#34;: \u0026#34;build.apprunner.amazonaws.com\u0026#34; }, \u0026#34;Action\u0026#34;: \u0026#34;sts:AssumeRole\u0026#34; } ] }\u0026#39; aws iam attach-role-policy \\ --role-name CampushubAppRunnerECRAccessRole \\ --policy-arn arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess Command pertama membuat IAM role dengan trust policy yang mengizinkan App Runner untuk meng-assume role tersebut. Command kedua meng-attach policy bawaan AWS yang memberikan akses read-only ke ECR.\nARN dari role yang baru dibuat akan digunakan sebagai AccessRoleArn pada konfigurasi App Runner service di langkah selanjutnya.\nMembuat App Runner Service # App Runner service bisa dibuat melalui AWS Console atau CLI. Supaya lebih mudahnya, berikut contoh jika menggunakan CLI.\nPertama, kita buat dahulu service untuk bacFWkend.\naws apprunner create-service \\ --service-name campushub-backend \\ --source-configuration \u0026#39;{ \u0026#34;AuthenticationConfiguration\u0026#34;: { \u0026#34;AccessRoleArn\u0026#34;: \u0026#34;arn:aws:iam::768052528660:role/CampushubAppRunnerECRAccessRole\u0026#34; }, \u0026#34;AutoDeploymentsEnabled\u0026#34;: true, \u0026#34;ImageRepository\u0026#34;: { \u0026#34;ImageIdentifier\u0026#34;: \u0026#34;768052528660.dkr.ecr.ap-southeast-1.amazonaws.com/campushub/backend:latest\u0026#34;, \u0026#34;ImageConfiguration\u0026#34;: { \u0026#34;Port\u0026#34;: \u0026#34;8000\u0026#34;, \u0026#34;RuntimeEnvironmentVariables\u0026#34;: { \u0026#34;APP_NAME\u0026#34;: \u0026#34;Campushub-API\u0026#34;, \u0026#34;APP_ENV\u0026#34;: \u0026#34;production\u0026#34;, \u0026#34;APP_KEY\u0026#34;: \u0026#34;[redacted]\u0026#34;, \u0026#34;APP_DEBUG\u0026#34;: \u0026#34;false\u0026#34;, \u0026#34;APP_TIMEZONE\u0026#34;: \u0026#34;Asia/Jakarta\u0026#34;, \u0026#34;APP_URL\u0026#34;: \u0026#34;[redacted]\u0026#34;, \u0026#34;DB_CONNECTION\u0026#34;: \u0026#34;mysql\u0026#34;, \u0026#34;DB_HOST\u0026#34;: \u0026#34;[redacted]\u0026#34;, \u0026#34;DB_PORT\u0026#34;: \u0026#34;[redacted]\u0026#34;, \u0026#34;DB_DATABASE\u0026#34;: \u0026#34;[redacted]\u0026#34;, \u0026#34;DB_USERNAME\u0026#34;: \u0026#34;[redacted]\u0026#34;, \u0026#34;DB_PASSWORD\u0026#34;: \u0026#34;[redacted]\u0026#34;, \u0026#34;JWT_SECRET\u0026#34;: \u0026#34;[redacted]\u0026#34;, \u0026#34;JWT_TTL\u0026#34;: \u0026#34;1440\u0026#34; } }, \u0026#34;ImageRepositoryType\u0026#34;: \u0026#34;ECR\u0026#34; } }\u0026#39; \\ --instance-configuration \u0026#39;{\u0026#34;Cpu\u0026#34;: \u0026#34;512\u0026#34;, \u0026#34;Memory\u0026#34;: \u0026#34;1024\u0026#34;}\u0026#39; \\ --region ap-southeast-1 Setelah service berhasil dibuat, App Runner akan memberikan URL default berformat https://\u0026lt;random-id\u0026gt;.\u0026lt;region\u0026gt;.awsapprunner.com. URL ini bisa diakses langsung atau dikonfigurasi dengan custom domain. URL dari backend dapat dimasukkan ke image frontend pada proses build. Sehingga, pada proses ini sebenarnya harus membuild ulang image frontend dan menguploadnya ke ECR kembali.\nJika sudah, maka bisa konfigurasi untuk service frontend.\naws apprunner create-service \\ --service-name campushub-frontend \\ --source-configuration \u0026#39;{ \u0026#34;AuthenticationConfiguration\u0026#34;: { \u0026#34;AccessRoleArn\u0026#34;: \u0026#34;arn:aws:iam::768052528660:role/CampushubAppRunnerECRAccessRole\u0026#34; }, \u0026#34;AutoDeploymentsEnabled\u0026#34;: true, \u0026#34;ImageRepository\u0026#34;: { \u0026#34;ImageIdentifier\u0026#34;: \u0026#34;768052528660.dkr.ecr.ap-southeast-1.amazonaws.com/campushub/frontend:latest\u0026#34;, \u0026#34;ImageConfiguration\u0026#34;: { \u0026#34;Port\u0026#34;: \u0026#34;3000\u0026#34; }, \u0026#34;ImageRepositoryType\u0026#34;: \u0026#34;ECR\u0026#34; } }\u0026#39; \\ --instance-configuration \u0026#39;{\u0026#34;Cpu\u0026#34;: \u0026#34;512\u0026#34;, \u0026#34;Memory\u0026#34;: \u0026#34;1024\u0026#34;}\u0026#39; \\ --region ap-southeast-1 Perlu dicatat bahwa, konfigurasi di atas artinya adalah:\nAutoDeploymentsEnabled diset true supaya App Runner otomatis melakukan deployment setiap kali image baru dipush ke ECR.\nCpu dan Memory dikonfigurasi dalam satuan milliCPU dan MB. 512 CPU berarti 0.5 vCPU, dan 1024 Memory berarti 1GB RAM.\nAccessRoleArn di AuthenticationConfiguration wajib ada untuk private ECR repository, karena App Runner membutuhkan role ini untuk menarik image dari ECR.\nSetelah deployment menggunakan App Runner telah berhasil, setiap kali image baru dipush ke ECR, App Runner akan mendeteksi perubahan dan melakukan rolling deployment tanpa adanya downtime. Sehingga, tidak perlu mengelola cluster, task definition, atau service update seperti di ECS.\nLalu, bagaimana dengan autoscalingnya? App Runner secara default sudah menggunakan konfigurasi autoscaling bernama DefaultConfiguration dengan parameter berikut:5\nParameter Default Keterangan MinSize 1 Jumlah minimum instance yang selalu berjalan MaxSize 25 Jumlah maksimum instance yang bisa discale MaxConcurrency 100 Batas concurrent request per instance sebelum scale up Artinya, dengan konfigurasi Campushub 0.5 vCPU dan 1GB RAM saat ini, App Runner akan selalu menjalankan minimal 1 instance. Ketika satu instance sudah menangani 100 concurrent request, App Runner otomatis menambah instance baru dengan spek yang sama. Proses ini berlanjut hingga mencapai batas maksimum 25 instance. Ketika traffic menurun, instance tambahan akan discale down kembali. Sebenarnya konfigurasi autoscaling ini bisa dicustom melalui create-auto-scaling-configuration jika diperlukan.\nWhat are Cloud Containers?\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nAWS App Runner Pricing\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nAWS Fargate Pricing\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nAmazon EKS Pricing\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nApp Runner Auto Scaling Configuration\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"29 September 2025","externalUrl":null,"permalink":"/posts/serverless-container/","section":"Semua Artikel","summary":"Terkadang, aplikasi yang seharusnya dideploy dengan infrastruktur yang simple, malah didapati dengan over-engineering. Tidak semua aplikasi, khususnya container, butuh cluster Kubernetes yang kompleks. Beberapa hanya butuh infrastruktur yang simple namun scalable. Bagaimana ya, implementasinya di AWS?\nArtikel ini adalah rangkuman dari presentasi POROS Filkom Study Group 2025. Berikut slide presentasi yang digunakan:\nPrevious Next ","title":"Tidak Semua Container Butuh Kubernetes. Beberapa Hanya Butuh Solusi yang Simple, Affordable, tapi Reliable seperti App Runner.","type":"posts"},{"content":"","date":"12 Mei 2025","externalUrl":null,"permalink":"/tags/ai/","section":"Tags","summary":"","title":"AI","type":"tags"},{"content":"","date":"12 Mei 2025","externalUrl":null,"permalink":"/tags/amazon-q/","section":"Tags","summary":"","title":"Amazon Q","type":"tags"},{"content":"Belajar coding itu kadang melelahkan. Wajar kalau butuh jeda dari rutinitas debugging yang tidak ada habisnya. Tapi, bagaimana kalau jeda itu tetap bisa mengasah kemampuan coding? Kenapa tidak menggabungkan belajar dan hiburan dalam satu petualangan coding yang sepenuhnya gamified?\nCode Quest Adventure adalah aplikasi game berbasis web yang menghibur sekaligus melatih kemampuan membaca dan mendebug kode di dalam kepala. Pemain harus menebak output dari sebuah kode dalam tantangan multiple-choice, sekaligus menemukan bagian yang hilang dari kode dalam tantangan fill-in-the-blank. Buktikan kemampuanmu untuk mengalahkan final boss dan raih gelar master developer.\nYang menarik, cerita, objektif, pertanyaan, dan jawaban dalam game ini di-generate secara real-time oleh Amazon Q Developer CLI. Setiap kali bermain, cerita dan pertanyaannya selalu berbeda. Tidak ada konten yang di-hardcode. Semuanya dihasilkan oleh Amazon Q Developer CLI, sehingga kemungkinan kontennya tidak terbatas dan selalu unik.\nDemo # Aturan mainnya sangat sederhana. Ada tiga level dalam game ini. Pemain harus mengalahkan final boss di level tiga. Perlu diperhatikan, damage dari musuh akan semakin besar seiring bertambahnya level.\nJika menjawab pertanyaan dengan benar, pemain mendapatkan poin dan musuh akan terkena damage.\nSebaliknya, jika jawaban salah, pemain yang akan terkena serangan dari musuh.\nRepositori # Source code Code Quest Adventure tersedia di repositori GitHub. Di dalamnya terdapat tiga file README yang mendokumentasikan kode frontend dan backend untuk keperluan testing dan review secara lokal.\nCara Kerja Amazon Q Developer # Bagaimana game ini bekerja? Bagaimana kontennya bisa di-generate oleh AI?\nSingkat cerita, project ini terdiri dari dua bagian, yaitu frontend dan backend. Aplikasi frontend mengirim request ke backend melalui API untuk menjalankan perintah q chat \u0026ndash;no-interactive di CLI. Amazon Q Developer CLI kemudian memberikan respons yang di-parse sebagai JSON oleh aplikasi backend. Data JSON tersebut dikirim kembali ke frontend sebagai response dan disajikan kepada pengguna.\nJujur, masih jauh dari kata master developer. Apalagi AI prompt engineer. Tapi dengan bantuan Amazon Q Developer, game ini bisa terwujud. Berikut adalah prompt awal yang digunakan untuk Amazon Q Developer:\nHelp me prepare an environment to build a simple web-based game. The game should be named with an interesting name that represents an adventure story game, learning coding, and Amazon Q. The programming language for the frontend should be JS. The backend will be using python. It is an interactive online code learning platform for beginners. It has to be 100% gamified. It is an adventure story based game. The question given to the user is not just multiple choice, but also fill in the blank. The application consists of frontend and backend parts with no login and register feature. The folder name should be lowercase with \u0026#34;-\u0026#34; as a delimiter. Initialize the project file using framework generator. Add some graphics mechanism, like the user will beat the enemy and go to the next level if the answer is correct. Otherwise, player\u0026#39;s hp will decrease to death. Frontend will ask backend to get the story, game content, and question, which is not stored in the database, but the code will ask Amazon Q in CLI to generate using command \u0026#34;q chat --no-interactive\u0026#34;. There must be no default or predefined response. If Amazon Q fails, just give an error message that will be displayed. While backend is generating a response, add a creative loading screen so user doesn\u0026#39;t get bored, like quotes or progress bar. Before building the frontend, verify all backend endpoints giving the correct response. Then, verify the frontend is also deployed without any errors and production ready. Don\u0026#39;t forget to add readme file. Cukup itu saja. Seluruh struktur project langsung tersedia. Karena menggunakan Amazon Q Developer CLI, proses persiapan development environment pun ikut terotomasi. Instalasi nodejs, inisialisasi framework menggunakan npm dan pip, semuanya ditangani. Amazon Q Developer bahkan sudah membuat kode awal untuk logika dasar dan beberapa grafis. Berikut tampilan project pada prompt pertama.\nTentu saja masih butuh banyak polesan supaya lebih menghibur. Dengan bantuan Amazon Q Developer, proses penyelesaian game ini jadi sangat mudah. Memasukkan grafis, menambahkan sound effect, debugging, dan memperbaiki logika game, semuanya bisa diselesaikan hanya dalam 2 hari.\nIni adalah pengalaman pertama menggunakan Amazon Q Developer, dan bisa dibilang ini adalah AI developer tool paling canggih yang pernah digunakan. Hampir semuanya ditangani. Menyiapkan development environment, menyusun struktur aplikasi, memperbaiki logika kode, hingga mendeploy aplikasi ke server EC2, semua dilakukan oleh Amazon Q Developer. Di balik layar, Amazon Q Developer bahkan menantang pemain untuk coding dengan mengenerate pertanyaan coding yang tricky di dalam game ini. Pada akhirnya, tool ini benar-benar mengajarkan cara build yang lebih baik dan lebih cepat.\n","date":"12 Mei 2025","externalUrl":null,"permalink":"/posts/code-quest-adventure/","section":"Semua Artikel","summary":"Belajar coding itu kadang melelahkan. Wajar kalau butuh jeda dari rutinitas debugging yang tidak ada habisnya. Tapi, bagaimana kalau jeda itu tetap bisa mengasah kemampuan coding? Kenapa tidak menggabungkan belajar dan hiburan dalam satu petualangan coding yang sepenuhnya gamified?\n","title":"Belajar Coding Sambil Main Game? Bisa, dengan Amazon Q Developer CLI","type":"posts"},{"content":"","date":"12 Mei 2025","externalUrl":null,"permalink":"/tags/game/","section":"Tags","summary":"","title":"Game","type":"tags"},{"content":"","date":"7 Oktober 2024","externalUrl":null,"permalink":"/tags/architecture/","section":"Tags","summary":"","title":"Architecture","type":"tags"},{"content":"","date":"7 Oktober 2024","externalUrl":null,"permalink":"/tags/infrastructure/","section":"Tags","summary":"","title":"Infrastructure","type":"tags"},{"content":" Baca juga: Memilih Tipe EC2 yang Tepat untuk General Purpose Server Membangun Infrastruktur dengan Docker Swarm Penyesuaian Docker Swarm Docker Compose dalam Swarm Dokumentasi Resmi Docker Load Balancing Docker Swarm Biaya tambahan penunjang infrastruktur\nProduk Deskripsi Sifat Periode Tagihan Harga Domain .com Membeli domain .com untuk kebutuhan komersial Wajib 1 Tahun Rp. 110.000 Google Workspace Domain email kustom untuk kebutuhan surat menyurat melalui gmail secara lebih professional Opsional 1 Bulan Rp. 100.000 Server Serbaguna Menambahkan 1 server (c7g.large) jika menginginkan platform pemantauan performa dan keamanan sistem Opsional 1 Bulan Rp. 1.000.000 Topologi # Aplikasi dengan muatan kerja yang cenderung ringan direncanakan menggunakan topologi yang melibatkan setidaknya tiga server yang dirinci sebagai berikut.\nSatu server untuk semua laboratorium yang berlangganan.\nSemua kode yang sudah siap produksi akan diunggah ke registry Gitlab. Client dari Gitlab akan memerintahkan Docker Master untuk menyebarkan kode ke Docker Worker.\nTopologi ini membutuhkan setidaknya dua server untuk tahap awal. Seiring berkembangnya infrastruktur, topologi ini akan membutuhkan autoscaling group dan load balancer untuk menyesuaikan diri dengan scaling horizontal.\nRencana Biaya Perawatan Infrastruktur # Setiap infrastruktur sebuah sistem pasti ada biaya perawatan rutinnya. Berikut ini adalah biaya perawatan infrastruktur setiap bulannya berdasarkan paket sewa.\nLaboratorium Dasar # Rp. 760.000\n✔ Up to 10 users ✔ 25GB Storage Limit\nLaboratorium Standar # Rp. 1.140.000\n✔ Up to 20 users ✔ 50GB Storage Limit\nLaboratorium Besar # Rp. 1.560.000\n✔ Up to 35 users ✔ 125GB Storage Limit\nLaboratorium Plus # Rp. 2.000.000\n✔ Up to 50 users ✔ 300GB Storage Limit\nRincian Spesifikasi dan Pengembangan Infrastruktur # Infrastruktur awal akan direncanakan memiliki spesifikasi awal sebagai berikut.\nLayanan Spesifikasi Harga Jumlah Subtotal EC2 t4g.small (2vCPU/2GB RAM) 280.000 2 Rp. 560.000 EBS 32GB GP3 60.000 2 Rp. 120.000 S3 25GB Standard 18.000 1 Rp. 18.000 Elastic IP Public IPv4 60.000 1 Rp. 60.000 Total Rp. 758.000 Pengembangan infrastruktur akan dilakukan seiring bertambahnya pengguna.\nJumlah Pengguna Spesifikasi Sebelumnya Speseifikasi setelah ditingkatkan 20 2X EC2 t4g.small 1X EC2 t4g.small1X EC2 m7g.medium 30 1X EC2 t4g.small1X EC2 m7g.medium 2X EC2 m7g.medium 40 2X EC2 m7g.medium2X 32GB GP3 1X EC2 c7g.large1X EC2 m7g.medium2X 48GB GP3 60 1X EC2 c7g.large1X EC2 m7g.medium 2X EC2 c7g.large 80 2X EC2 c7g.large2X 48GB GP3 2X EC2 m7g.large2X 64GB GP3 100 - Autoscaling ","date":"7 Oktober 2024","externalUrl":null,"permalink":"/posts/simulasitopologi/","section":"Semua Artikel","summary":" Baca juga: Memilih Tipe EC2 yang Tepat untuk General Purpose Server Membangun Infrastruktur dengan Docker Swarm Penyesuaian Docker Swarm Docker Compose dalam Swarm Dokumentasi Resmi Docker Load Balancing Docker Swarm Biaya tambahan penunjang infrastruktur\n","title":"Simulasi Perencanaan Anggaran dan Pengembangan Infrastruktur Berbasis Cloud","type":"posts"},{"content":"","date":"7 Oktober 2024","externalUrl":null,"permalink":"/tags/topology/","section":"Tags","summary":"","title":"Topology","type":"tags"},{"content":"","date":"6 Februari 2024","externalUrl":null,"permalink":"/tags/ec2/","section":"Tags","summary":"","title":"EC2","type":"tags"},{"content":"Dalam membangun infrastruktur cloud menggunakan AWS EC2, satu keputusan penting yang perlu diambil adalah memilih tipe instance yang sesuai dengan kebutuhan. Tipe instance EC2 yang dipilih akan mempengaruhi performa aplikasi yang dijalankan. Tipe instance yang ditawarkan oleh AWS EC2 sangat beragam dari instance dengan sumber daya komputasi yang besar hingga yang hemat biaya, setiap tipe memiliki karakteristik yang perlu dipertimbangkan.1\nGeneral purpose server pakai tipe apa? # Singkat cerita, tipe yang cocok untuk server yang akan menjalankan aplikasi general purpose/tujuan umum adalah tipe C, M dan R.2\nPilih tipe C jika memiliki sedikit budget, dan instance mementingkan sumber daya komputasi yang kuat Pilih tipe M jika memiliki budget pas-pasan, dan menginginkan instance dengan CPU dan RAM yang ideal Pilih tipe R jika memiliki banyak budget, dan instance ditujukan sebagai database server\nTipe Perbandingan vCPU : RAM (GB) Harga/vCPU/Bulan Penggunaan Sesuai C 1 : 2 Rp. 500.000 High-performance computing M 1 : 4 Rp. 600.000 Balanced general purpose R 1 : 8 Rp. 750.000 High-performance database Pertimbangan harga selengkapnya telah dirangkum pada laman situs Perbandingan tipe AWS EC2\nApa kabar seri T? # Singkatnya, tergantung pemakaian, seri T bisa menguntungkan, namun di sisi lain bisa merugikan.\nMasalah pertama, harga\nJika dilihat dalam daftar harga, tentu saja tipe T memiliki harga yang jauh lebih murah. Bahkan, tipe t3.large(2vCPU 8GB RAM) hanya dibanderol seharga $0.083/jam (us-east-1) atau setara dengan Rp.940.000/bulan. AWS mengklaim bahwa seri T lebih hemat sebanyak 15% dibandingkan dengan seri M.\nSeri T memiliki baseline, yaitu batasan maksimal (5-40%) sumber daya instance tipe T dapat berjalan secara \u0026ldquo;gratis\u0026rdquo; tanpa kredit. Karena, seri T hanya bisa menggunakan seluruh sumber daya yang dimilikinya dalam jangka waktu tertentu menggunakan satuan kredit.3\nJika penggunaan CPU dibawah baseline, maka kredit burst bertambah Jika penggunaan CPU sama dengan baseline, maka kredit burst diam Jika penggunaan CPU lebih dari baseline, maka kredit burst berkurang Tabel *baseline* pada instance t4g Instance Size vCPU RAM Baseline Performance / vCPU Network Burst Bandwidth (Gbps) EBS Burst Bandwidth (Mbps) t4g.nano 2 0.5 5% Up to 5 Up to 2,085 t4g.micro 2 1 10% Up to 5 Up to 2,085 t4g.small 2 2 20% Up to 5 Up to 2,085 t4g.medium 2 4 20% Up to 5 Up to 2,085 t4g.large 2 8 30% Up to 5 Up to 2,780 t4g.xlarge 4 16 40% Up to 5 Up to 2,780 t4g.2xlarge 8 32 40% Up to 5 Up to 2,780 Tabel kredit pada instance t4g Instance type CPU credits earned per hour Maximum earned credits that can be earned in 24-hour t4g.nano 6 144 t4g.micro 12 288 t4g.small 24 576 t4g.medium 24 576 t4g.large 36 864 t4g.xlarge 96 2304 t4g.2xlarge 192 4608 Setiap 1 kredit digunakan untuk \u0026ldquo;membayar\u0026rdquo; penggunaan sumber daya dengan keterangan sebagai berikut:\n1 kredit = 1 vCPU * 100% utilization * 1 minute. 1 kredit = 1 vCPU * 50% utilization * 2 minutes 1 kredit = 2 vCPU * 25% utilization * 2 minutes Ketika kredit burst sudah habis, maka sumber daya yang dapat digunakan adalah sekian persen (5-40%) sesuai dengan ukuran instance yang disewa.4 Namun jika ingin menambah burst kredit, pengguna dapat beralih ke mode tanpa batas dan dikenakan biaya tambahan sesuai pemakaian, yaitu sebesar $0.05/jam/vCPU untuk T2 dan t3, $0.04/jam/vCPU untuk t4.5\n\u0026ldquo;If you are pegging the instance at 100% for a solid month, an M instance will be a better choice due to what you will pay in unlimited credits charges with a T. M instances cost a little more but aren\u0026rsquo;t metered in any way so you can run them as much as you like at a predictable rate.\u0026rdquo;6\nContohnya seperti ini, tipe T2 varian small (1vCPU / 2GB RAM) dibanderol seharga $0.03/jam atau setara dengan Rp.340.000/bulan. Biaya ekstra yang diperlukan untuk menjalankan 100% performa T2.small selama seharian penuh adalah $1.32/hari atau setara dengan Rp.620.000. Maka, harga sebenarnya yang dikeluarkan untuk mendapatkan 100% performa T2.small untuk satu bulan penuh mencapai Rp.960.000 (Hampir setara dengan harga c7g.large(2vCPU 4GB RAM)!.\nTipe t2.nano t2.micro t3.small CPU Credits per Hour 3 6 12 vCPUs 1 1 1 Baseline % of CPU 5% 10% 20% Cost $ per Hour $0.0058 $ 0.0116 $0.0232 vCPU Hours (Baseline) 1.2 2.4 4.8 vCPU Hours (Earned Credits) 1.14 2.16 3.84 vCPU Hours (Total Included) 2.34 4.56 8.64 Baseline Cost $ per CPU Hour $0.0025 $0.0025 $0.0027 Baseline Cost per day $0.1392 $0.2784 $0.5568 Baseline Hours per Day 24 24 24 vCPU Hours to Buy 21.66 19.44 15.36 Unlimited Cost$ per vCPU Hour $0.05 $0.05 $0.05 Upcharge to Run at 100% $1.0830 $0.9720 $0.7680 Prorated Hourly Cost at 100% $0.0509 $0.0521 $0.0552 Cost to Run at 100% All Day $1.2222 $1.2504 $1.3248 Melihat hasil pengujian pada tipe T yang dilakukan oleh Cast AI dengan beban kerja Kubernetes7 dan pengujian Coiled dengan beban kerja rekayasa data,8 tipe T sangat tidak cocok untuk menangani beban kerja yang banyak dan terus-menerus, karena mengakibatkan terjunnya performa dan membengkaknya biaya pemakaian.\nPada pengujian oleh beban kerja rekayasa data, Coiled menjalankan serangkaian benchmark Python menggunakan t3.large dan m6i.large. Kurang lebih ada 100 benchmark yang mencakup ilmu data umum, rekayasa data, dan beban kerja machine learning. Proses normalnya memerlukan waktu sekitar 150 jam. Pada akhirnya, biaya yang dikeluarkan untuk menjalankan benchmark pada tipe T adalah sebesar Rp.110.000, membengkak hampir dua kali lipat daripada tipe M yang hanya sebesar Rp.62.000.\nBagaimana jika tidak membeli kredit dan hanya membayar harga dasar dari tipe T? Yang bener aja, rugi dong! Tipe T hanya menghemat biaya sekitar 15%, sedangkan performa yang dipotong sebesar 60-95%. Gila banget.\nMasalah kedua, performa\nPengujian yang dilakukan Coiled juga menunjukkan sumber daya yang dipakai tipe T masih kurang bagus. Benchmark yang dijalankan oleh m6i.large bisa terselesaikan dalam kurun waktu 140 jam, sedangkan t3.large memakan waktu selama 170 jam! Hal ini terlihat dari perbandingan performa disk dan network kedua instance.\nm6i.large memiliki kecepatan disk dan network yang lebih cepat daripada t3.large. Hasil benchmarking selengkapnya dapat dilihat pada platform Grafana milik Coiled untuk instance t3 dan instance m6. Processor yang digunakan m6i juga jauh lebih canggih dan cepat dibandingkan dengan processor yang dipakai pada t3.\nInstance Processor t3.large Up to 3.1 GHz Intel Xeon Scalable processor (Skylake 8175M or Cascade Lake 8259CL) m6i.large Up to 3.5 GHz 3rd Generation Intel Xeon Scalable processors (Ice Lake 8375C) Pada saat artikel ini ditulis, seri terbaru tipe T (t4g) menggunakan Amazon Graviton 2, padahal pada seri terbaru tipe lain (M7g,C7g) sudah memakai Amazon Graviton 3, generasi terbaru dari processor tersebut saat ini. Bahkan, seri t4g hanya menyediakan 5 Gbps network bandwidth di setiap ukuran instancenya.\nBeberapa pengguna instance tipe T mengalami masalah performa khususnya pada penggunaan CPU yang mengalami throttling, yaitu ketika sumber daya CPU tidak bisa digunakan 100%. Padahal, instance tipe T yang digunakan telah disetel ke mode tak terbatas.\nPara pengguna berpendapat hal ini terjadi karena adanya sistem berbagi sumber daya/shared CPU. Bahwa sebenarnya 5-40% dari baseline sumber daya CPU yang dijanjikan benar-benar diperuntukkan oleh satu penyewa, namun sisa sumber daya CPU diluar baseline tersebut hanya dapat dipakai tergantung seberapa banyak sumber daya sebenarnya yang sedang kosong dan yang sedang digunakan oleh penyewa lain.\n\u0026ldquo;T-instance CPUs are massively oversubscribed, meaning they sell more CPUs than what exists on the hardware - that is the whole point, that\u0026rsquo;s why they have baseline utilization and bursting and why they are cheaper. I would guess they probably do have the baseline utilization reserved for you, and anything above that depends on how much CPU time the hardware has free.\u0026rdquo;9\n\u0026ldquo;Amazon support confirmed that if we want to have CPU guaranteed, we need to switch away from a burstable instance. They did not say how much is guaranteed for burstable. So I would say the answer is: \u0026ldquo;There is no guarantee for any CPU on a burstable instance\u0026rdquo;. They seem to use \u0026ldquo;baseline utilization\u0026rdquo; as some kind of soft target, but even that seems not guaranteed.\u0026rdquo;10\nJadi, siapa yang cocok menggunakan tipe T?\nTentu saja server dengan beban kerja yang dilakukan relatif ringan dengan konsumsi sumber daya instance dibawah baseline dan memiliki sedikit peak hour (1-3 jam/hari) bisa mendapatkan keuntungan dari tipe T. Merujuk pada penjelasan sebelumnya, t4g.large dapat menyimpan kredit gratis hingga 864 kredit, dan 1 kredit dapat digunakan untuk membeli sumber daya di atas baseline sebanyak 100% untuk 1 menit.\nKarena t4g.large punya 2vCPU, maka dibutuhkan 2 kredit untuk menggunakan 100% kapasitasnya selama 1 menit. Dengan begitu dapat diketahui bahwa t4g.large dapat menggunakan 100% kapasitasnya dengan gratis selama 432 menit (864 kredit/2 vCPU) atau setara dengan 7 jam 12 menit.\nNamun, perlu diingat bahwa 864 kredit itu dikumpulkan selama 24 jam jika instance tidak melebihi baseline. Setiap tipe T mendapatkan kredit gratis dalam jumlah tertentu setiap jamnya. Dalam kasus ini, t4g.large dapat mengumpulkan kredit hingga 864 kredit dengan pendapatan 36 kredit setiap jamnya. Maka t4g.large akan mengisi penuh kembali kreditnya dalam jangka waktu 24 jam (864 kredit max/36 kredit per jam).11\nIntel, AMD, atau Graviton? # Amazon Web Services mengklaim bahwa prosesor Graviton lebih cocok untuk beban kerja application servers, microservices, open-source databases, dan high performance computing. Amazon Graviton juga diklaim mampu menghemat biaya sebesar 20% daripada menggunakan prosesor berbasis x86 di instance Amazon EC2, dan 60% lebih hemat energi.12\nMichael Larabel pada tanggal 22 Mei 2022 mempublikasikan hasil benchmarking ketiga processor yang terdapat pada banyak tipe instance AWS EC2, yaitu Graviton, Graviton 2, Graviton 3, Intel Xeon, dan AMD EPYC dengan sistem operasi Ubuntu 22.04 LTS dan masing-masing pada tipe instance 4xlarge. Hasil lebih lengkap dapat dibaca pada situs Open Benchmarking atau situs Phoronix.\nDari 94 benchmark yang dijalankan di seluruh instance EC2 yang diuji, Graviton 3 menempati posisi pertama terbanyak dengan 43 kemenangan, disusul Intel Xeon dengan 35 kemenangan dan AMD EPYC dengan 16 kemenangan. Rata-rata geometrik dari seluruh 94 hasil benchmark, Graviton3 sedikit mengungguli Intel Xeon dan diikuti oleh AMD EPYC. Dengan serangkaian 94 benchmark yang dilakukan, Graviton3 43% lebih cepat dibandingkan instance Graviton2 berukuran sama dan 3,1x performa instance Graviton original.\nPada laman Compute - Amazon EC2 Instance Types tertera bahwa setiap vCPU pada Instance EC2 Graviton-Based adalah sebuah core pada AWS Graviton processor, sedangkan setiap vCPU pada Instance EC2 yang tidak berbasis Graviton adalah thread pada processor berbasis x86, dengan pengecualian beberapa tipe.\n\u0026ldquo;Each vCPU on Graviton-based Amazon EC2 instances is a core of AWS Graviton processor.\u0026rdquo;\n\u0026ldquo;Each vCPU on non-Graviton-based Amazon EC2 instances is a thread of x86-based processor.\u0026rdquo;13\nCore adalah unit pemrosesan yang ada pada CPU, sedangkan threads adalah unit pemrosesan terkecil yang berbentuk suatu rangkaian instruksi virtual. Pada sistem Amazon EC2 yang berbasis x86, satu vCPU merujuk kepada threads yang merepresentasikan setengah core. Instance yang menerapkan sistem ini mengunakan sistem symmetric multi-processing, yang membagi satu core menjadi dua core virtual untuk meningkatkan performa. Dengan teknologi ini, OS dapat memetakan threads kedalam vCPU yang berbeda-beda.\nKesimpulannya, Instance EC2 yang berbasis Graviton memiliki sumber daya yang lebih baik. Sebagai contoh, instance c7i yang memiliki 8vCPU sebenarnya hanya akan menggunakan 4 core CPU fisik. Tapi, instance c7g yang memiliki 8 vCPU akan benar-benar menggunakan 8 core CPU fisik. Artinya, instance c7g mendapatkan kapasitas penuh sebuah core CPU fisik untuk setiap vCPU yang membuatnya lebih powerful!.\nRagam tipe instance AWS EC2\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nPenjelasan mendetail tipe AWS EC2\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nKonsep instance dengan unlimited busrt performance\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nRincian baseline setiap seri instance tipe T\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nDaftar harga burst pada instance tipe T\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nKomentar pengguna tipe T di Reddit\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nPengujian burst vs non-burst dengan beban kerja Kubernetes\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nPengujain burst vs non-burst dengan beban kerja rekayasa data\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nKomentar pengguna tipe T di Stackoverflow\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nPendapat pengguna tipe T di Stackoverflow\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nPerincian kalkulasi kredit dan baseline\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nKeunggulan Processor Graviton\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nRincian processor instance EC2\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"6 Februari 2024","externalUrl":null,"permalink":"/posts/panduan-ec2/","section":"Semua Artikel","summary":"Dalam membangun infrastruktur cloud menggunakan AWS EC2, satu keputusan penting yang perlu diambil adalah memilih tipe instance yang sesuai dengan kebutuhan. Tipe instance EC2 yang dipilih akan mempengaruhi performa aplikasi yang dijalankan. Tipe instance yang ditawarkan oleh AWS EC2 sangat beragam dari instance dengan sumber daya komputasi yang besar hingga yang hemat biaya, setiap tipe memiliki karakteristik yang perlu dipertimbangkan.1\n","title":"Memilih Tipe EC2 yang Tepat untuk General Purpose Server","type":"posts"},{"content":"Dalam mengelola instance EC2, pemahaman mengenai penamaan tipe instance sangat penting.\nTipe instance AWS EC2 memiliki sejumlah penamaan yang mungkin terdengar kompleks pada awalnya, tapi pemahaman yang baik tentang struktur dan arti di balik penamaan tersebut akan memberikan keputusan yang tepat dalam menyusun infrastruktur cloud sesuai dengan kebutuhan. Berikut adalah perincian setiap digit pada penamaan instance Amazon EC2:\nDigit pertama Kode Arti C CPU optimized M Middle (General purpose) R RAM (Memory optimized) T Burstable performance D Dense storage F FPGA G Graphics intensive I Storage optimized IM Storage optimized with a one to four ratio of vCPU to memory IS Storage optimized with a one to six ratio of vCPU to memory INF AWS Inferentia MAC macOS P GPU accelerated TRN AWS Trainium U High memory VT Video transcoding X Memory intensive HPC High Performance Computing Digit kedua Nomor gen (Contoh; t3 = instance burstable gen ke-3 ) Digit ketiga Kode Arti A AMD processors G Amazon Graviton processors I Intel processors Digit keempat Kode Arti D Instance store volumes N Network and EBS optimized E Extra storage or memory Z High performance Q Qualcomm inference accelerators FLEX Flex instance Referensi:\nPanduan Pengguna AWS EC2 ","date":"6 Februari 2024","externalUrl":null,"permalink":"/posts/penamaan-ec2/","section":"Semua Artikel","summary":"Dalam mengelola instance EC2, pemahaman mengenai penamaan tipe instance sangat penting.\nTipe instance AWS EC2 memiliki sejumlah penamaan yang mungkin terdengar kompleks pada awalnya, tapi pemahaman yang baik tentang struktur dan arti di balik penamaan tersebut akan memberikan keputusan yang tepat dalam menyusun infrastruktur cloud sesuai dengan kebutuhan. Berikut adalah perincian setiap digit pada penamaan instance Amazon EC2:\n","title":"Panduan membaca penamaan tipe instance AWS EC2","type":"posts"}]