คอนเทนเนอร์· ~30 นาที
เสิร์ฟ VOD ผ่าน Ingress + หน้าเว็บ player
ติดตั้ง ingress-nginx เปิด bucket ให้อ่านได้ และสร้างหน้าเว็บอัปโหลด+ดูวิดีโอครบวงจร
ชิ้นสุดท้าย: ให้คนดูวิดีโอผ่านเบราว์เซอร์ได้จริง — ติดตั้ง Ingress controller เป็นประตูเดียวของระบบ แล้ว route ตาม path: / ไปหน้าเว็บ+API และ /vod ตรงไปที่ MinIO (เพราะ HLS เป็นไฟล์ static ไม่จำเป็นต้องผ่าน API ให้เปลืองแรง)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
# รอจน controller พร้อม
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=120sเปิด bucket vod ให้อ่านแบบ public
ผู้ชมต้องโหลด .m3u8/.ts ได้โดยไม่ต้อง login — ใช้ Pod ชั่วคราว (kubectl run) รัน mc เข้าไปตั้ง bucket policy:
kubectl run mc --rm -it --image=minio/mc --restart=Never --command -- /bin/sh
# ได้ shell ข้างใน Pod แล้วพิมพ์:
mc alias set store http://minio:9000 admin password123
mc anonymous set download store/vod
exit # Pod ลบตัวเองอัตโนมัติเพราะ --rmเพิ่ม endpoint รายการวิดีโอใน API
// คืนรายการ videoId ที่แปลงเสร็จแล้ว (มี index.m3u8 ใน bucket vod)
app.get('/api/videos', (_req, res) => {
const ids = new Set()
const stream = minio.listObjectsV2('vod', '', true)
stream.on('data', (obj) => {
if (obj.name.endsWith('index.m3u8')) ids.add(obj.name.split('/')[0])
})
stream.on('end', () => res.json([...ids]))
stream.on('error', (err) => res.status(500).json({ error: err.message }))
})หน้าเว็บครบวงจร: อัปโหลด + รายการ + player
<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mini YouTube บน K8s</title>
<style>
body { font-family: sans-serif; max-width: 760px; margin: 2rem auto; padding: 0 1rem; }
li { margin: .3rem 0; } #msg { color: #059669; }
</style>
<h1>🎬 Mini YouTube บน K8s</h1>
<form id="up">
<input type="file" name="video" accept="video/mp4" required>
<button>อัปโหลด</button>
</form>
<p id="msg"></p>
<h2>วิดีโอทั้งหมด <button onclick="refresh()">รีเฟรช</button></h2>
<ul id="list"></ul>
<video id="player" controls width="720"></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script>
<script>
async function refresh() {
const videos = await (await fetch('/api/videos')).json()
list.innerHTML = videos
.map((v) => '<li><a href="#" onclick="play(\'' + v + '\');return false">▶ ' + v + '</a></li>')
.join('') || '<li>ยังไม่มีวิดีโอ — อัปโหลดเลย!</li>'
}
function play(id) {
const src = '/vod/' + id + '/index.m3u8'
if (player.canPlayType('application/vnd.apple.mpegurl')) {
player.src = src
} else if (Hls.isSupported()) {
const hls = new Hls()
hls.loadSource(src)
hls.attachMedia(player)
}
player.play()
}
up.onsubmit = async (e) => {
e.preventDefault()
msg.textContent = '⏳ กำลังอัปโหลด...'
const r = await fetch('/api/upload', { method: 'POST', body: new FormData(up) })
const d = await r.json()
msg.textContent = '✅ videoId=' + d.videoId + ' กำลังแปลงไฟล์อยู่ — รอครู่นึงแล้วกดรีเฟรช'
}
refresh()
</script>Ingress: ประตูเดียว สอง path
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: video
namespace: video
spec:
ingressClassName: nginx
rules:
- http:
paths:
- path: /vod
pathType: Prefix
backend:
service:
name: minio
port: { number: 9000 }
- path: /
pathType: Prefix
backend:
service:
name: video-api
port: { number: 80 }docker build -t video-api:v1 ./api
kind load docker-image video-api:v1 --name video
kubectl rollout restart deployment/video-api
kubectl apply -f k8s/ingress.yaml
# เปิดเบราว์เซอร์ → http://localhost:8080
# อัปโหลด sample.mp4 → รอ Job วิ่งจบ (kubectl get jobs -w) → รีเฟรช → กดเล่น 🎉สรุป Key Takeaways
- Ingress = ประตูเดียว route ตาม path: / → API, /vod → MinIO ตรง ๆ
- ไฟล์ static ไม่ควรไหลผ่าน API — pattern เดียวกับ CDN หน้า S3 ใน production
- bucket ผลลัพธ์เปิด anonymous download ได้ แต่ bucket ต้นฉบับยังปิดไว้
อ่านจบแล้วอย่าลืมทำเครื่องหมาย

