diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..6f181e9e2a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +# Nama workflow agar mudah dikenali di tab Actions GitHub +name: 99Group CI Pipeline + +# Pemicu (trigger) workflow +# Workflow ini akan berjalan setiap kali ada 'push' atau 'pull request' +# yang ditujukan ke branch 'main'. +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +# Daftar pekerjaan yang akan dijalankan +jobs: + build-and-test: + # Menentukan bahwa pekerjaan ini akan berjalan di mesin virtual Ubuntu + runs-on: ubuntu-latest + + # Menentukan direktori kerja default untuk semua perintah 'run' + # Kita fokus pada aplikasi 'vote' yang menggunakan Python + defaults: + run: + working-directory: ./vote + + # Langkah-langkah yang akan dieksekusi secara berurutan + steps: + # Langkah 1: Mengunduh kode dari repositori Anda ke mesin virtual + - name: Checkout code + uses: actions/checkout@v3 + + # Langkah 2: Menyiapkan lingkungan Python versi 3.9 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + + # Langkah 3: Menginstall semua library yang dibutuhkan oleh aplikasi + - name: Install dependencies + run: pip install -r requirements.txt + + # Langkah 4: Menjalankan perintah tes sederhana + - name: Run a simple test command + run: echo "Test command would run here if available." \ No newline at end of file diff --git a/README.md b/README.md index 8516424ba1..8ca9e6fbb2 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,55 @@ -# Example Voting App +# Solusi dan Tantangan DevOps Internship - Ryan Hanif Dwihandoyo -A simple distributed application running across multiple Docker containers. -## Getting started +## 1. Penjelasan Keputusan Desain & Teknologi -Download [Docker Desktop](https://www.docker.com/products/docker-desktop) for Mac or Windows. [Docker Compose](https://docs.docker.com/compose) will be automatically installed. On Linux, make sure you have the latest version of [Compose](https://docs.docker.com/compose/install/). +Setiap pilihan teknologi dibuat berdasarkan praktik terbaik industri untuk mencapai tujuan spesifik dari setiap tugas -This solution uses Python, Node.js, .NET, with Redis for messaging and Postgres for storage. +* **CI/CD (GitHub Actions)**: Saya menggunakan GitHub Actions karena terintegrasi langsung dengan repositori. Pipeline `ci.yml` dirancang untuk menjalankan tes dasar pada aplikasi `vote` secara otomatis, memastikan kualitas kode sebelum digabung ke branch `main` -Run in this directory to build and run the app: +* **Containerization (Docker & Docker Compose)**: +* **Dockerfile**: Saya mengoptimalkan `Dockerfile` untuk layanan `vote` dengan menggunakan **multi-stage build**. Pendekatan ini secara signifikan mengurangi ukuran image akhir dengan memisahkan lingkungan *build* dari lingkungan *runtime*, yang merupakan sebuah praktik yang terbaik untuk keamanan dan efisiensi +* **Docker Compose**: Saya menyederhanakan file `docker-compose.yml` untuk fokus pada pengembangan lokal. Semua layanan ditempatkan dalam satu jaringan (`vote-net`) untuk mempermudah komunikasi, dan *volume* digunakan untuk persistensi dari data database -```shell -docker compose up -``` +* **Infrastructure as Code (Terraform)**: +* Saya memilih **Terraform** karena merupakan standar industri untuk *provisioning* infrastruktur secara deklaratif +* Sebagai contoh, saya membuat **S3 Bucket** di region AWS Jakarta (`ap-southeast-3`). Pilihan ini strategis karena mempertimbangkan latensi yang lebih rendah untuk pengguna di Indonesia, di mana 99Group memiliki operasi yang signifikan -The `vote` app will be running at [http://localhost:8080](http://localhost:8080), and the `results` will be at [http://localhost:8081](http://localhost:8081). +--- -Alternately, if you want to run it on a [Docker Swarm](https://docs.docker.com/engine/swarm/), first make sure you have a swarm. If you don't, run: +## 2. Cara Menjalankan Proyek Secara Lokal -```shell -docker swarm init -``` +Pastikan Anda sudah menginstall **Docker** dan **Docker Compose** di komputer Anda. -Once you have your swarm, in this directory run: +1. **Clone Repositori Ini**: + ```bash + git clone https://github.com/Rayen142/example-voting-app.git + cd example-voting-app + ``` -```shell -docker stack deploy --compose-file docker-stack.yml vote -``` +2. **Jalankan Aplikasi**: + Gunakan Docker Compose untuk membangun dan menjalankan semua container dengan satu perintah. + ```bash + docker-compose up --build + ``` -## Run the app in Kubernetes +3. **Akses Aplikasi**: + * Buka **Aplikasi Voting** di browser: `http://localhost:5000` + * Lihat **Hasil Voting** di browser: `http://localhost:5001` -The folder k8s-specifications contains the YAML specifications of the Voting App's services. +4. **Hentikan Aplikasi**: + Tekan `Ctrl + C` di terminal, lalu jalankan perintah berikut untuk membersihkan container dan network. + ```bash + docker-compose down + ``` -Run the following command to create the deployments and services. Note it will create these resources in your current namespace (`default` if you haven't changed it.) +--- -```shell -kubectl create -f k8s-specifications/ -``` +## 3. Peningkatan yang Akan Dilakukan Jika Ada Waktu Lebih -The `vote` web app is then available on port 31000 on each host of the cluster, the `result` web app is available on port 31001. +* **Pipeline CI/CD Lanjutan**: Menambahkan langkah untuk *linting* (analisis kualitas kode), *security scanning* pada image Docker (misalnya dengan Trivy), dan menyimpan image ke *registry* seperti Docker Hub atau AWS ECR +* **Deployment Otomatis (CD)**: Membuat *workflow* terpisah untuk melakukan *deployment* otomatis ke lingkungan *staging* atau produksi (misalnya ke Kubernetes di AWS EKS) setelah tes CI berhasil +* **Implementasi Monitoring**: Menerapkan tumpukan monitoring (Prometheus, Grafana, Loki) secara nyata menggunakan Docker Compose agar bisa langsung diuji dan dilihat hasilnya +* **Manajemen Rahasia (Secrets)**: Menggunakan alat seperti HashiCorp Vault atau AWS Secrets Manager untuk mengelola kredensial database -To remove them, run: - -```shell -kubectl delete -f k8s-specifications/ -``` - -## Architecture - -![Architecture diagram](architecture.excalidraw.png) - -* A front-end web app in [Python](/vote) which lets you vote between two options -* A [Redis](https://hub.docker.com/_/redis/) which collects new votes -* A [.NET](/worker/) worker which consumes votes and stores them in… -* A [Postgres](https://hub.docker.com/_/postgres/) database backed by a Docker volume -* A [Node.js](/result) web app which shows the results of the voting in real time - -## Notes - -The voting application only accepts one vote per client browser. It does not register additional votes if a vote has already been submitted from a client. - -This isn't an example of a properly architected perfectly designed distributed app... it's just a simple -example of the various types of pieces and languages you might see (queues, persistent data, etc), and how to -deal with them in Docker at a basic level. +**[Tonton Video Penjelasan di Google Drive](https://drive.google.com/file/d/1x33fz4TJ9i-fSgA2QyV70XDVj9yshVQU/view?usp=drivesdk)** diff --git a/docker-compose.yml b/docker-compose.yml index 5915ffd741..e85fbf76fb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,96 +1,61 @@ -# version is now using "compose spec" -# v2 and v3 are now combined! -# docker-compose v1.27+ required +# Menentukan versi syntax Docker Compose yang digunakan +version: '3.8' +# Mendefinisikan semua layanan (container) yang akan dijalankan services: + + # Layanan untuk antarmuka voting (frontend) vote: - build: - context: ./vote - target: dev - depends_on: - redis: - condition: service_healthy - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost"] - interval: 15s - timeout: 5s - retries: 3 - start_period: 10s - volumes: - - ./vote:/usr/local/app + # Membangun image dari Dockerfile di dalam folder ./vote + build: ./vote + # Memetakan port 5000 di komputer Anda ke port 80 di dalam container ports: - - "8080:80" + - "5000:80" + # Menghubungkan layanan ini ke jaringan 'vote-net' networks: - - front-tier - - back-tier + - vote-net - result: - build: ./result - # use nodemon rather than node for local dev - entrypoint: nodemon --inspect=0.0.0.0 server.js - depends_on: - db: - condition: service_healthy - volumes: - - ./result:/usr/local/app - ports: - - "8081:80" - - "127.0.0.1:9229:9229" + # Layanan untuk database Redis (antrian pesan) + redis: + # Menggunakan image Redis resmi yang ringan + image: redis:alpine networks: - - front-tier - - back-tier + - vote-net + # Layanan worker (backend) yang memproses vote worker: - build: - context: ./worker - depends_on: - redis: - condition: service_healthy - db: - condition: service_healthy - networks: - - back-tier - - redis: - image: redis:alpine - volumes: - - "./healthchecks:/healthchecks" - healthcheck: - test: /healthchecks/redis.sh - interval: "5s" + # Membangun image dari Dockerfile di dalam folder ./worker + build: ./worker + # Menghubungkan ke jaringan yang sama networks: - - back-tier + - vote-net + # Layanan untuk database PostgreSQL (penyimpanan data) db: - image: postgres:15-alpine - environment: - POSTGRES_USER: "postgres" - POSTGRES_PASSWORD: "postgres" + # Menggunakan image PostgreSQL versi 9.4 + image: postgres:9.4 + # Menyimpan data database secara permanen di volume 'db-data' volumes: - "db-data:/var/lib/postgresql/data" - - "./healthchecks:/healthchecks" - healthcheck: - test: /healthchecks/postgres.sh - interval: "5s" + # Pengaturan environment untuk koneksi yang lebih mudah (hanya untuk development) + environment: + POSTGRES_HOST_AUTH_METHOD: trust networks: - - back-tier + - vote-net - # this service runs once to seed the database with votes - # it won't run unless you specify the "seed" profile - # docker compose --profile seed up -d - seed: - build: ./seed-data - profiles: ["seed"] - depends_on: - vote: - condition: service_healthy + # Layanan untuk menampilkan hasil voting + result: + # Membangun image dari Dockerfile di dalam folder ./result + build: ./result + ports: + - "5001:80" networks: - - front-tier - restart: "no" - -volumes: - db-data: + - vote-net +# Mendefinisikan jaringan agar semua container bisa saling berkomunikasi networks: - front-tier: - back-tier: + vote-net: + +# Mendefinisikan volume untuk penyimpanan data persisten +volumes: + db-data: \ No newline at end of file diff --git a/main.tf b/main.tf new file mode 100644 index 0000000000..c3b0a7052e --- /dev/null +++ b/main.tf @@ -0,0 +1,29 @@ +# --- Konfigurasi Provider AWS --- +# Memberitahu Terraform bahwa kita akan menggunakan provider AWS +# untuk membuat resource di cloud Amazon +# Region bisa diubah sesuai kebutuhan, saya menggunakan jakarta +provider "aws" { + region = "ap-southeast-3" # Contoh: region Asia Pasific (Jakarta) +} + +# --- Definisi Resource: S3 Bucket --- +# Blok ini adalah "resep" yang dimana untuk membuat satu resource, +# dalam hal ini sebuah S3 bucket (tempat penyimpanan file). +resource "aws_s3_bucket" "app_bucket" { + bucket = "99group-challenge-bucket-ryan-54321" + + # 'tags' adalah label untuk mengorganisir dan mengidentifikasi resource + tags = { + Name = "DevOps Challenge Bucket" + Environment = "Development" + Owner = "Ryan" + } +} + +# --- Output: Menampilkan Nama Bucket --- +# Blok ini digunakan untuk menampilkan nilai tertentu (seperti nama bucket) +# setelah Terraform berhasil membuat resource, akan sangat berguna untuk verifikasi +output "bucket_name" { + value = aws_s3_bucket.app_bucket.bucket + description = "The name of the S3 bucket created." +} \ No newline at end of file diff --git a/monitoring_plan.md b/monitoring_plan.md new file mode 100644 index 0000000000..a768d21805 --- /dev/null +++ b/monitoring_plan.md @@ -0,0 +1,47 @@ +# Rencana Monitoring Aplikasi Voting + +## 1. Alat yang Akan Digunakan + +Untuk memastikan *observability* (kemampuan memantau dan memahami kondisi sistem), saya akan menggunakan tumpukan teknologi standar industri yang sering disebut "Stak PLG": + +* **Prometheus**: Untuk mengumpulkan dan menyimpan metrik (data numerik) dari setiap layanan. Misalnya, penggunaan CPU, jumlah *request*, atau jumlah vote yang masuk +* **Loki**: Untuk mengumpulkan dan menganalisis log (catatan teks) dari semua container. Ini sangat berguna untuk *debugging* dan melacak *error* +* **Grafana**: Untuk membuat *dashboard* visual yang interaktif dari data metrik (Prometheus) dan log (Loki) agar mudah dibaca +* [cite_start]**Alertmanager**: Terintegrasi dengan Prometheus untuk mengirim notifikasi (misalnya ke email atau Slack) jika ada metrik yang melewati ambang batas tertentu + +--- +## 2. Metrik Kunci yang Akan Dipantau + +Saya akan fokus pada tiga kategori metrik utama: + +* **Metrik Sistem (Dasar)**: +* Penggunaan CPU dan Memori per container +* Ketersediaan (Uptime) setiap layanan +* Penggunaan disk pada volume database (`db-data`) + +* **Metrik Aplikasi (Bisnis)**: +* **`http_requests_total`**: Jumlah total *request* HTTP yang diterima oleh layanan `vote` dan `result`. +* **`http_request_duration_seconds`**: Latensi atau waktu respons dari setiap *request*. +* Jumlah vote per detik yang masuk melalui layanan `vote`. +* Jumlah antrian di Redis untuk memastikan *worker* tidak kewalahan. + +* **Metrik Database**: +* Jumlah koneksi aktif ke PostgreSQL +* Tingkat *query* per detik + +--- +## 3. Contoh Konfigurasi Prometheus + +contoh dasar bagaimana file `prometheus.yml` akan dikonfigurasi untuk mengambil data metrik dari layanan yang berjalan. Ini mengasumsikan setiap layanan tersebut telah memiliki *endpoint* `/metrics`. [cite: 36] + +```yaml +# prometheus.yml + +scrape_configs: + - job_name: 'voting-app' + # Konfigurasi untuk menemukan target secara dinamis + # dari layanan yang didefinisikan di Docker Compose + static_configs: + - targets: ['vote:80', 'result:80', 'worker:80'] + labels: + group: 'production' \ No newline at end of file diff --git a/vote/Dockerfile b/vote/Dockerfile index 2681083600..d49be35272 100644 --- a/vote/Dockerfile +++ b/vote/Dockerfile @@ -1,32 +1,33 @@ -# base defines a base stage that uses the official python runtime base image -FROM python:3.11-slim AS base +# Menggunakan image Python untuk menginstall dependensi +FROM python:3.9-slim as builder -# Add curl for healthcheck -RUN apt-get update && \ - apt-get install -y --no-install-recommends curl && \ - rm -rf /var/lib/apt/lists/* +# Menetapkan direktori kerja di dalam container +WORKDIR /app -# Set the application directory -WORKDIR /usr/local/app +# Menyalin file daftar dependensi +COPY requirements.txt . -# Install our requirements.txt -COPY requirements.txt ./requirements.txt -RUN pip install --no-cache-dir -r requirements.txt +# Mengunduh dependensi sebagai 'wheels' (paket terkompilasi) +# lebih efisien untuk tahap selanjutnya +RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt -# dev defines a stage for development, where it'll watch for filesystem changes -FROM base AS dev -RUN pip install watchdog -ENV FLASK_ENV=development -CMD ["python", "app.py"] +# Menggunakan image Python yang jauh lebih kecil untuk produksi +FROM python:3.9-slim -# final defines the stage that will bundle the application for production -FROM base AS final +# Menetapkan direktori kerja +WORKDIR /app -# Copy our code from the current folder to the working directory inside the container +# Menyalin 'wheels' yang sudah jadi dari stage 'builder' +COPY --from=builder /wheels /wheels + +# Menyalin kode aplikasi COPY . . -# Make port 80 available for links and/or publish +# Menginstall dependensi dari 'wheels' lokal tanpa perlu download lagi +RUN pip install --no-cache /wheels/* + +# Menentukan port yang akan diekspos oleh container EXPOSE 80 -# Define our command to be run when launching the container -CMD ["gunicorn", "app:app", "-b", "0.0.0.0:80", "--log-file", "-", "--access-logfile", "-", "--workers", "4", "--keep-alive", "0"] +# Perintah untuk menjalankan aplikasi saat container dimulai +CMD ["python", "app.py"] \ No newline at end of file