DE

[Tech Blog #16 | Data Engineering] Docker Căn Bản Cho Dân Dữ Liệu: Làm Chủ Container, Volume & Dựng Data Stack Trong 1 Phút

0 views
[Tech Blog #16 | Data Engineering] Docker Căn Bản Cho Dân Dữ Liệu: Làm Chủ Container, Volume & Dựng Data Stack Trong 1 Phút

1. Đặt vấn đề: Nỗi ám ảnh "Nó chạy ngon trên máy tôi mà!" (It works on my machine)

Đã bao giờ bạn gặp tình huống trớ trêu này chưa:

  • Bạn viết một script Python ETL kết nối PostgreSQL chạy mượt mà trên laptop của bạn (dùng MacOS / Windows).
  • Khi bàn giao cho đồng nghiệp chạy thử hoặc đẩy lên server Ubuntu, hàng loạt lỗi đỏ rực ập đến: lệch phiên bản Python, thiếu thư viện driver C (psycopg2), sai múi giờ hệ thống, hoặc port 5432 bị chiếm dụng bởi một phần mềm khác.

Trước đây, để giải quyết vấn đề này, người ta thường dùng Máy ảo (Virtual Machine - VM như VMware, VirtualBox). Nhưng mỗi máy ảo lại ngốn hàng chục GB dung lượng và khởi động mất vài phút vì phải cõng nguyên một hệ điều hành riêng biệt.

Đó là lý do Docker ra đời và trở thành "vua không ngai" trong việc đóng gói và triển khai ứng dụng dữ liệu.


2. Giải mã 4 khái niệm sống còn của Docker bằng ví dụ đời thường

Để làm chủ Docker, bạn chỉ cần ghi nhớ 4 khái niệm trực quan sau:

[Dockerfile]  ──(Build)──>  [Docker Image]  ──(Run)──>  [Docker Container]
(Công thức làm bánh)       (Khuôn đúc đông lạnh)       (Chiếc bánh ăn được đang chạy)
                                                               │
                                  ┌────────────────────────────┴──────────────────────────┐
                                  ▼                                                       ▼
                       [Port Mapping: 5432:5432]                               [Volume: ./data:/var/lib]
                    (Cửa thông từ ngoài vào container)                       (Chiếc USB cắm ngoài lưu data vĩnh viễn)
  1. Docker Image (Bản vẽ / Khuôn đúc):

    • Là một file nén chỉ đọc (Read-only) chứa toàn bộ mã nguồn, thư viện, biến môi trường và runtime cần thiết để chạy một phần mềm.
    • Ví dụ: Image postgres:16 đã đóng gói sẵn hệ điều hành Linux siêu nhẹ cùng phần mềm PostgreSQL được cấu hình chuẩn.
  2. Docker Container (Ngôi nhà đang vận hành):

    • Là một phiên bản thực thi (Instance) được sinh ra từ Image.
    • Container hoàn toàn cô lập với máy thật của bạn. Bạn có thể bật 5 container PostgreSQL cùng lúc trên 1 máy mà chúng không hề xung đột với nhau.
  3. Port Mapping (Ánh xạ cổng):

    • Container như một căn phòng kín có tường lửa bao quanh.
    • Lệnh -p 5432:5432 nghĩa là: "Hãy mở một cánh cửa nối từ cổng 5432 của máy tính thật (Host) vào thẳng cổng 5432 bên trong phòng của Container".
  4. Docker Volume (Bộ nhớ lưu trữ vĩnh viễn):

    • Bản chất của Container là tạm bợ (Ephemeral). Nếu bạn tắt và xóa container đi, toàn bộ dữ liệu database bên trong sẽ biến mất không dấu vết.
    • Volume giống như chiếc USB cắm ngoài: Nó gắn một thư mục trên ổ cứng máy thật vào container. Khi container bị xóa hay nâng cấp phiên bản mới, dữ liệu database trong Volume vẫn còn nguyên 100%.

3. Bảng so sánh: Máy ảo (Virtual Machine) vs. Docker Container

Tiêu chíMáy ảo truyền thống (Virtual Machine)Docker Container
Kiến trúcCần một hệ điều hành khách (Guest OS) riêng biệt cho mỗi máy ảo.Dùng chung nhân hệ điều hành (Host OS Kernel) với máy tính chủ.
Dung lượng lưu trữRất nặng (Thường từ vài GB đến vài chục GB mỗi máy).Siêu nhẹ (Chỉ từ vài chục MB đến vài trăm MB).
Thời gian khởi độngVài phút (phải boot lại toàn bộ OS).1 đến 3 giây (chỉ khởi chạy một tiến trình phần mềm).
Tài nguyên CPU / RAMPhải phân bổ cứng (ví dụ cấp riêng 4GB RAM dù không xài hết).Dùng bao nhiêu ăn bấy nhiêu (Dynamic allocation).

4. Thực hành thực chiến: Dựng trọn bộ PostgreSQL + Metabase bằng Docker Compose

Thay vì phải gõ những câu lệnh docker run dài dòng dễ nhầm lẫn trong terminal, công cụ Docker Compose cho phép bạn khai báo toàn bộ hệ thống cơ sở dữ liệu và công cụ BI trong duy nhất một file docker-compose.yml.

Bước 1: Tạo file cấu hình docker-compose.yml

version: '3.8'

services:
  # ============================================================================
  # SERVICE 1: CƠ SỞ DỮ LIỆU POSTGRESQL
  # ============================================================================
  postgres_db:
    image: postgres:16-alpine           # Dùng bản Alpine siêu nhẹ (~80MB)
    container_name: de_postgres_db
    restart: always                     # Tự khởi động lại nếu gặp sự cố
    environment:
      POSTGRES_DB: analytics_warehouse  # Tên database khởi tạo sẵn
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: SecretPassword123
    ports:
      - "5432:5432"                    # Ánh xạ cổng 5432 máy thật vào container
    volumes:
      - postgres_data:/var/lib/postgresql/data # Gắn Volume để dữ liệu không bị mất khi xóa container
    networks:
      - data_network

  # ============================================================================
  # SERVICE 2: CÔNG CỤ TRỰC QUAN HÓA DỮ LIỆU METABASE
  # ============================================================================
  metabase_bi:
    image: metabase/metabase:latest
    container_name: de_metabase_bi
    restart: always
    ports:
      - "3000:3000"                    # Truy cập giao diện web tại http://localhost:3000
    environment:
      MB_DB_TYPE: h2                   # Metabase dùng cơ sở dữ liệu nội bộ riêng để lưu dashboard
    depends_on:
      - postgres_db                    # Đợi PostgreSQL sẵn sàng thì Metabase mới bật
    networks:
      - data_network

# Khai báo volume lưu trữ vĩnh viễn trên ổ cứng
volumes:
  postgres_data:
    driver: local

# Mạng nội bộ riêng giúp 2 container nói chuyện với nhau thông qua tên service
networks:
  data_network:
    driver: bridge

Bước 2: Khởi chạy toàn bộ hệ thống bằng 1 lệnh duy nhất

Mở terminal tại thư mục chứa file docker-compose.yml và chạy:

# Lệnh khởi động toàn bộ stack ở chế độ chạy ngầm (detached mode)
docker compose up -d

Chỉ sau 30 giây:

  • PostgreSQL đã sẵn sàng nhận kết nối tại localhost:5432.
  • Bạn mở trình duyệt vào địa chỉ http://localhost:3000 là có ngay giao diện Metabase BI đẹp mắt để kết nối vào Postgres vẽ biểu đồ!

Bước 3: Script Python tự động nạp dữ liệu vào Database chạy trên Docker

import psycopg2
from datetime import datetime

# Thông tin kết nối khớp chính xác với file docker-compose.yml ở trên
DB_CONFIG = {
    "host": "localhost",
    "port": 5432,
    "dbname": "analytics_warehouse",
    "user": "admin",
    "password": "SecretPassword123"
}

def setup_and_seed_data():
    print(f"[{datetime.now()}] Đang kết nối tới PostgreSQL bên trong Docker...")
    try:
        conn = psycopg2.connect(**DB_CONFIG)
        cursor = conn.cursor()
        
        # 1. Tạo bảng doanh thu mẫu
        create_table_sql = """
        CREATE TABLE IF NOT EXISTS daily_kpi_summary (
            date_key DATE PRIMARY KEY,
            total_orders INT NOT NULL,
            revenue_vnd NUMERIC(15, 2) NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        );
        """
        cursor.execute(create_table_sql)
        
        # 2. Chèn dữ liệu mẫu
        insert_sql = """
        INSERT INTO daily_kpi_summary (date_key, total_orders, revenue_vnd)
        VALUES (%s, %s, %s)
        ON CONFLICT (date_key) DO UPDATE SET
            total_orders = EXCLUDED.total_orders,
            revenue_vnd = EXCLUDED.revenue_vnd;
        """
        sample_records = [
            ("2026-09-23", 145, 45200000.00),
            ("2026-09-24", 182, 58900000.00),
            ("2026-09-25", 210, 72500000.00)
        ]
        
        cursor.executemany(insert_sql, sample_records)
        conn.commit()
        print("Đã nạp thành công dữ liệu mẫu vào PostgreSQL trong Docker!")
        
        cursor.close()
        conn.close()
    except Exception as e:
        print(f"Lỗi kết nối hoặc thực thi: {e}")

if __name__ == "__main__":
    setup_and_seed_data()

5. Câu hỏi phỏng vấn thực tế (Interview Q&A)

Câu 1 (Mức độ: Entry / Junior)

Câu hỏi: "Phân biệt sự khác nhau cốt lõi giữa Docker Image và Docker Container? Cho ví dụ minh họa."

Gợi ý trả lời:
"Docker Image là một bản đóng gói tĩnh chỉ đọc (Blueprint / Snapshot) chứa mã nguồn, thư viện và môi trường thực thi. Nó giống như một chiếc đĩa CD cài đặt game hoặc một bản thiết kế ngôi nhà trên giấy.
Trong khi đó, Docker Container là một phiên bản sống đang chạy được khởi tạo từ Image đó. Nó có trạng thái riêng, bộ nhớ riêng và có thể được bắt đầu (start), tạm dừng (stop), hoặc xóa bỏ (rm).
Từ một Docker Image duy nhất (ví dụ: postgres:16), chúng ta có thể khởi chạy nhiều Container độc lập cùng lúc phục vụ cho các môi trường khác nhau như Dev, Testing và Staging."

Câu 2 (Mức độ: Junior / Mid-level)

Câu hỏi: "Tại sao khi chạy cơ sở dữ liệu (như PostgreSQL, MySQL hay ClickHouse) bằng Docker, chúng ta bắt buộc phải sử dụng Docker Volume?"

Gợi ý trả lời:
"Bởi vì hệ thống tệp bên trong một Docker Container có tính chất tạm bợ (Ephemeral / Stateless).
Nếu chúng ta lưu dữ liệu trực tiếp trong container, khi container đó bị tắt, bị xóa đi do lỗi hoặc cần nâng cấp phiên bản Image mới, toàn bộ dữ liệu database bên trong sẽ bị xóa sạch theo.
Docker Volume cung cấp cơ chế lưu trữ bền vững (Data Persistence) bằng cách ánh xạ thư mục dữ liệu bên trong container ra ngoài ổ cứng của máy chủ chủ quản (Host OS). Nhờ đó, dữ liệu hoàn toàn độc lập với vòng đời của container: chúng ta có thể thoải mái xóa container cũ, tải bản vá lỗi mới về dựng container mới lên mà dữ liệu kinh doanh vẫn an toàn nguyên vẹn."


6. Key Takeaways (Ghi nhớ nhanh)

  1. Docker triệt tiêu lỗi môi trường: Đóng gói code cùng toàn bộ thư viện phụ thuộc, đảm bảo chạy ở đâu cũng cho kết quả y hệt nhau.
  2. Container là tạm bợ, Volume là vĩnh viễn: Chạy Database trên Docker thì bắt buộc phải gắn Volume (volumes:) nếu không muốn mất trắng dữ liệu.
  3. Docker Compose là vũ khí tối thượng: Dùng duy nhất một file docker-compose.yml để khởi động cả hệ sinh thái dữ liệu (Database + BI tool) chỉ bằng câu lệnh docker compose up -d.
  4. Cô lập mạng an toàn: Các container trong cùng một file compose tự động nhìn thấy nhau qua tên service (ví dụ: Metabase kết nối tới host postgres_db) mà không cần hardcode địa chỉ IP.
0Claps