การจัดการคอนเทนเนอร์และคลัสเตอร์ (Containers & Kubernetes)
5.4 การจัดการคอนเทนเนอร์และโฮสต์ (Docker & Kubernetes)
Kubernetes และ Docker คือมาตรฐานทองคำในการรัน Data Workloads ในปัจจุบัน บทนี้พาไปดูวิธีรัน Airflow, Spark, และ Flink บน K8s Cluster พร้อมระบบ Auto-scaling และ Health Management
- Step 1: Data Ingestion & Event Processing
- Step 2: Distributed Computation & State Management
- Step 3: Orchestration & Resource Allocation
1. กลไกเคอร์เนลของ Docker: Namespaces, Cgroups, และ Copy-On-Write
Learning Progression
- [BASIC] ปูพื้นฐานภาษาเข้าใจง่าย - เข้าใจคอนเซปต์ภาพรวมและการแก้ปัญหาเบื้องต้น
- [INTERMEDIATE] โค้ด/คอนฟิกไวยากรณ์จริง - การเขียนโค้ดเพื่อใช้งานจริงในระบบ
- [PROFESSIONAL] Under-the-hood & Performance/FinOps - กลไกเบื้องลึกและการรีดประสิทธิภาพ
Real-World Enterprise Scenario
เคสระบบการเงิน/Big Tech: การรองรับ Transaction จำนวนมหาศาลต่อวินาทีพร้อมประกัน Data Integrity สูงสุด โดยใช้สถาปัตยกรรมที่ยืดหยุ่นและการมอนิเตอร์ระดับสูง
ทฤษฎีและกลไกการทำงาน (How it works): เทคโนโลยีคอนเทนเนอร์ไม่ใช่การจำลอง OS ทั้งหมด (Virtual Machine) แต่เป็นโพรเซสของระบบปฏิบัติการโฮสต์ที่ถูกกักบริเวณด้วยฟีเจอร์ของ Linux Kernel: - **Namespaces**: กั้นขอบเขตการมองเห็น (Isolation) เช่น โปรเซส (PID), เน็ตเวิร์ก (NET), ดิสก์ (MNT) ไม่ให้มองเห็นกันข้ามคอนเทนเนอร์ - **Control Groups (Cgroups)**: บังคับโควต้าทรัพยากร (เช่น ห้ามกินแรมเกิน 4GB, จำกัด CPU cycles) - **Copy-On-Write (CoW)**: โครงสร้างไฟล์เลเยอร์ย่อยที่ยอมให้อ่านและเขียนเฉพาะส่วนต่าง ช่วยประหยัดพื้นที่ดิสก์และทำให้คอนเทนเนอร์สตาร์ทได้ในพริบตา
# Running a Docker container with strict CPU and RAM cgroup limits
docker run -d \
--name etl-worker-prod \
--memory="4g" \
--cpus="2" \
pipecraft/etl-runner:v1.0
Use Case ในชีวิตจริง (Real-world Scenario): ในเซิร์ฟเวอร์รันระบบท่อข้อมูลหลักของบริษัทที่มีแรมจำกัด 32GB การใช้ Cgroups ของ Docker ครอบท่อประมวลผลช่วยป้องกันไม่ให้สคริปต์ Python ที่เผลอโหลดข้อมูลใหญ่เกินไปสูบแรมเครื่องแม่จนทำหน้าเว็บ API ล่มไปด้วย
ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations): คอนเทนเนอร์มีสถานะจัดเก็บชั่วคราว (Ephemeral) ข้อมูลในนั้นจะหายวับไปหากคอนเทนเนอร์ดับ แก้ไขโดยห้ามเซฟข้อมูลสำคัญลงในตู้ แต่ให้เขียนข้อมูลออกไปเก็บบน S3 หรือเชื่อมต่อ Docker Volumes เข้ากับโฮสต์
2. Docker Multi-stage Build Optimization
ทฤษฎีและกลไกการทำงาน (How it works): การสร้าง Docker Image สำหรับ Production ต้องการความเบาและปลอดภัย แนวทางปฏิบัติที่ดีที่สุดคือ **Multi-Stage Build**: ในเลเยอร์แรก (Builder Stage) เราจะติดตั้งคอมไพเลอร์ที่หนักหน่วงเพื่อบิวด์โค้ด จากนั้นในเลเยอร์ที่สอง (Runner Stage) เราจะดึงมาแค่ผลลัพธ์ (Binary/Libraries) ที่บิวด์เสร็จแล้ว มาใส่ใน OS เปล่าๆ (เช่น Alpine หรือ Distroless) ทิ้งเครื่องมือแฮกเกอร์ไว้เบื้องหลัง
# Production-ready Multi-Stage Dockerfile for a Python ETL job
# Stage 1: Build dependencies environment
FROM python:3.9-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y gcc python3-dev
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Stage 2: Clean execution runtime
FROM python:3.9-slim AS runner
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY etl_script.py .
ENV PATH=/root/.local/bin:$PATH
USER 10001
CMD ["python", "etl_script.py"]
Use Case ในชีวิตจริง (Real-world Scenario): อิมเมจรันโมเดล Machine Learning มีขนาด 2GB เมื่อเปลี่ยนมาใช้ Multi-Stage Build สามารถรีดขนาดเหลือเพียง 300MB ลดระยะเวลา Pull Image ลงระบบ Kubernetes จาก 2 นาทีเหลือ 15 วินาที
ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations): ห้ามใช้สิทธิ์ `root` (สิทธิ์แอดมิน) รันกระบวนการในคอนเทนเนอร์ เพราะหากโดนเจาะระบบ แฮกเกอร์จะยึดเครื่องแม่ได้ แก้ไขโดยประกาศบรรทัด `USER 10001` เพื่อจำกัดสิทธิ์ในเลเยอร์รันไทม์เสมอ
3. สถาปัตยกรรม Kubernetes: Control Plane และ Worker Nodes
ทฤษฎีและกลไกการทำงาน (How it works): Kubernetes จัดการสเกลคลัสเตอร์เครื่องแม่ข่าย: - **Control Plane**: แกนกลางบริหารระบบ ประกอบด้วย `kube-apiserver` (รับคำสั่ง), `kube-scheduler` (ตัดสินใจว่าจะวาง Pod ที่เครื่องไหน), และ `etcd` (ฐานข้อมูลเก็บสถานะทั้งคลัสเตอร์) - **Worker Nodes**: เครื่องลูกข่ายที่มี `kubelet` คอยเปิดปิดตู้คอนเทนเนอร์ตามคำสั่ง - **Pod**: หน่วยประมวลผลย่อยที่สุดใน K8s ซึ่งอาจบรรจุได้ 1 หรือหลายตู้คอนเทนเนอร์ที่จำเป็นต้องแชร์ IP และ Volume ร่วมกัน
# Kubernetes Deployment defining resources for an API pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: data-api
spec:
replicas: 3
template:
spec:
containers:
- name: api-server
image: pipecraft/api:v2
resources:
requests: { memory: "512Mi", cpu: "500m" }
limits: { memory: "1Gi", cpu: "1000m" }
Use Case ในชีวิตจริง (Real-world Scenario): หากโหนด (Worker) ตัวหนึ่งฮาร์ดแวร์ไหม้และพังลง K8s Control Plane จะตรวจพบการขาดหายไปของจังหวะชีพจร และทำการคืนชีพ (Self-healing) โยกย้าย Pod ไปสร้างใหม่ที่โหนดอื่นอย่างรวดเร็วโดยไม่ต้องใช้คนสั่ง
ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations): หากไม่ได้ใส่คอนฟิก `resources.requests` และ `resources.limits` Pod อาจแย่งสูบแรมกันเองจนเครื่องแม่พังรหัส OOMKilled แก้ไขโดยต้องบังคับระบุโควต้าของทุกคอนเทนเนอร์อย่างเคร่งครัด
4. Kubernetes CRDs & Operators (Spark / Airflow Operators)
ทฤษฎีและกลไกการทำงาน (How it works): - **CRDs (Custom Resource Definitions)**: เป็นการสอนให้ K8s รู้จักอ็อบเจ็กต์ประเภทใหม่ๆ ที่ไม่ใช่แค่ Pod หรือ Deployment ทั่วไป - **K8s Operators**: เป็นโปรแกรม (Controller) ที่รันคอยเฝ้าดู CRDs เหล่านั้นและช่วยรันความซับซ้อนของการตั้งค่าซอฟต์แวร์แทนมนุษย์ (Automated Operations) เช่น การเปิดคลัสเตอร์ Spark หรือ Airflow บน Kubernetes
# SparkApplication CRD managed by Spark Operator
apiVersion: "sparkoperator.k8s.io/v1beta2"
kind: SparkApplication
metadata:
name: pyspark-etl-job
spec:
type: Python
pythonVersion: "3"
mode: cluster
image: "pipecraft/spark-job:latest"
mainApplicationFile: "local:///opt/spark/jobs/main.py"
driver:
cores: 1
memory: "1024m"
executor:
instances: 3
cores: 2
memory: "2048m"
Use Case ในชีวิตจริง (Real-world Scenario): แทนที่จะต้องเขียนสคริปต์มาตั้งค่า Master/Worker ในคลัสเตอร์ Spark เอง ทีมข้อมูลแค่เขียน YAML สร้างอ็อบเจ็กต์ประเภท `SparkApplication` แล้วส่งให้ Spark Operator ตัว Controller จะทำการเปิด Pod Driver 1 ตัวและ Executor 3 ตัวให้เอง และปิดทิ้งทั้งหมดเมื่อรันจ๊อบสำเร็จ
ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations): การลง Operator หลายตัวข้ามเวอร์ชันอาจเกิดการชนกันของสิทธิ์ Role-Based Access Control (RBAC) ภายในคลัสเตอร์ แก้ไขโดยจำกัด Namespace ของ Operators ให้อยู่เฉพาะพื้นที่ที่อนุญาตเท่านั้น
5. CSI Persistent Volumes & Stateful Storage ใน Kubernetes
ทฤษฎีและกลไกการทำงาน (How it works): ปกติ K8s เหมาะกับงานไร้สถานะ (Stateless) แต่ในโลกข้อมูลเราต้องการเก็บข้อมูลถาวร เช่น การรันระบบฐานข้อมูลบน K8s ระบบจะใช้มาตรฐาน **Container Storage Interface (CSI)** เพื่อสื่อสารกับผู้ให้บริการดิสก์ (เช่น AWS EBS, Google PD) โดยอัตโนมัติ โดยอ็อบเจ็กต์ **PersistentVolumeClaim (PVC)** จะทำการร้องขอขนาดพื้นที่ดิสก์ และ CSI จะทำการเสกดิสก์มาต่อเข้ากับ Pod ให้โดยตรง
# Requesting persistent storage via PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: database-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
storageClassName: premium-rwo # CSI driver provisions an SSD automatically
Use Case ในชีวิตจริง (Real-world Scenario): รันระบบ Apache Kafka Broker บน Kubernetes หาก Pod ล่มและถูกสปินขึ้นที่เครื่องใหม่ ระบบ K8s จะสั่งให้ CSI ดึงเอาดิสก์ EBS ก้อนเดิมขนาด 500GB ถอดจากเครื่องเก่ามาแปะที่โหนดใหม่ (Detach/Attach) ทำให้ข้อมูลข้อความไม่สูญหายไปไหน
ข้อควรระวังและวิธีแก้ (Pitfalls & Mitigations): โหมดการเข้าถึง `ReadWriteOnce` อนุญาตให้ Pod เพียงตัวเดียวเมานท์ดิสก์เท่านั้น หาก Pod สร้างใหม่ติดสถานะค้าง (Pending) เพราะโหนดเก่าล่มและดิสก์ยังไม่หลุด แก้ไขโดยตั้งเวลารอให้ระบบเตะดิสก์หลุด (Timeout) หรือใช้โหมด `ReadWriteMany` (NFS) ถ้างานต้องการเปิดไฟล์ร่วมกัน
Weekend Sandbox Challenge: Deploy Spark Operator CRD
โจทย์ปฏิบัติการ: จงเขียนสเปคไฟล์ Kubernetes YAML โดยประยุกต์ใช้ SparkApplication CRD ในการสั่งจองทรัพยากรรันจ๊อบ Spark แบบกระจายโหนด
# spark-job.yaml
apiVersion: "sparkoperator.k8s.io/v1beta2"
kind: SparkApplication
metadata:
name: monthly-aggregations
spec:
type: Python
pythonVersion: "3"
mode: cluster
image: "pipecraft/spark-aggregator:latest"
mainApplicationFile: "local:///opt/spark/jobs/aggregate.py"
driver:
cores: 1
memory: "512m"
executor:
instances: 2
cores: 1
memory: "1024m"
Senior Technical Interview Q&A
Q1: ทำไมการใช้ Multi-Stage Build จึงเป็นมาตรฐานสำคัญในโลกของการทำ Containerization สำหรับ Data Engineering?
A1: การโหลดข้อมูล Data Pipeline มักใช้ไลบรารีทางคณิตศาสตร์เช่น Pandas, Numpy หรือ C++ binding ที่ต้องใช้คอมไพเลอร์ในการบิวด์ ซึ่งมีขนาดไฟล์ใหญ่เป็นกิกะไบต์ การใช้ Multi-Stage ช่วยแยกเลเยอร์ตอนคอมไพล์ ออกจากเลเยอร์รันไทม์ ทำให้ Image ปลายทางไม่มีเครื่องมือที่ไม่จำเป็น ลดพื้นผิวการโจมตีทางไซเบอร์ (Attack surface) และประหยัดเวลา/ค่าใช้จ่ายในการ Pull image มหาศาล
Q2: จงอธิบายกลไกของ Kubernetes Operators ว่ามีความแตกต่างจาก Deployments พื้นฐานอย่างไร?
A2: Deployment พื้นฐานใช้เปิด/ปิดแอปพลิเคชันแบบ Stateless ง่ายๆ แต่ระบบข้อมูลเช่น Spark หรือ Kafka มีสถานะและลำดับขั้นตอนสลับซับซ้อน (เช่น ต้องเปิด Zookeeper ก่อนแล้วค่อยเปิด Broker) Operators จะเขียนตรรกะระดับซอฟต์แวร์ลงใน Controller เสมือนมีวิศวกรเฝ้าคลัสเตอร์ตลอดเวลา (Human Operator) เพื่ออ่าน CRDs และบริหารจัดการสเกล/แบ็คอัป ของระบบแบบ Stateful อัตโนมัติ
Interactive Dockerfile Image Builder
Interactive Docker
เป้าหมาย: เติมคำสั่ง Dockerfile (เช่น FROM, COPY, CMD) เพื่อกำหนดสเปคแพ็กเกจโปรแกรม Python ETL Pipeline ให้รันบนคอนเทนเนอร์
ข้อดี / จุดเด่น & ข้อเสีย / ข้อควรระวัง
ข้อดี / จุดเด่น
- รับประกันการรันของสคริปต์ประมวลผลข้อมูลได้สม่ำเสมอในทุกเครื่องระบบปฏิบัติการ
- ง่ายต่อการทำ Auto-scaling ขยายจำนวนประมวลผลและกู้คืนงานเมื่อพังอัตโนมัติ
ข้อเสีย / ข้อควรระวัง
- มีอัตราการเรียนรู้วิธีตั้งค่าและบริหารคลัสเตอร์ Kubernetes ที่ยากและซับซ้อนมาก
- การจัดการพื้นที่ดิสก์จัดเก็บอิมเมจเก่าๆ อาจทำให้เซิร์ฟเวอร์เต็มได้หากไม่มีระบบเก็บกวาด
ปฏิบัติการจริง (Lab Practice)
แล็บ: เขียน Dockerfile และประกอบประกอบอิมเมจท่อข้อมูล
วิธีรันแล็บปฏิบัติการบนเครื่องจริง (Local Terminal Execution Blueprint)
เนื่องจากปฏิบัติการ Data Engineering / DevOps ระดับสูงต้องรันบน Environment จริง ขั้นตอนด้านล่างนี้คือคำสั่งสำหรับนำไปรันบน Terminal / Docker ในเครื่องของคุณ:
mkdir -p pipecraft-lab && cd pipecraft-lab python3 -m venv venv && source venv/bin/activate pip install --upgrade pip pandas polars pytest requests
# Run execution pipeline test
python3 -c "
import polars as pl
print(' [PipeCraft Lab] Running local pipeline engine...')
df = pl.DataFrame({'id': [1, 2, 3], 'status': ['SUCCESS', 'SUCCESS', 'AUDITED']})
print(df)
"
# Verify clean execution exit status echo " PipeCraft Local Lab Execution Completed Successfully!"
💡 คำแนะนำ & ทริกเด็ด
ในการเขียน Dockerfile ให้ระบุคำสั่งติดตั้ง Dependencies (เช่น RUN pip install) ไว้ตอนต้นก่อนคำสั่งก๊อปปี้โค้ด (COPY . .) เพื่อให้ Docker ช่วยทำ Cache เลเยอร์ติดตั้งห้องสมุด ช่วยย่นเวลาบิวด์อิมเมจในอนาคตเหลือเพียงหลักวินาที