Angular 12 with Minikube Docker Driver
เพื่อนๆที่รู้จัก K8s หรือ Kubernetes คงรู้จัก Minikube อยู่แล้ว หนนี้ผมอยากจะรัน Angular 12 ใน container ที่ local เครื่องผมเองครับ
เตรียมเครื่องมือ
- Angular CLI (โหลดและติดตั้ง ที่นี่)
- Minikube (โหลดและอ่าน ที่นี่)
- Docker Desktop (โหลดและติดตั้ง ที่นี่)
- เครื่อง macOS 2018 (เพื่อนคนไหนใช้ Windows ใน Doc เขาก็แนะนำไว้แล้ว)
ng --version
ผล
Angular CLI: 12.2.0
Node: 14.17.1
Package Manager: npm 6.14.13
OS: darwin x64
minikube version
ผล
minikube version: v1.17.1
docker -v
ผล
Docker version 20.10.7
เตรียมความรู้
เริ่มต้นด้วยการลบ driver เก่าออกเพื่อติดตั้ง Docker driver ตัวใหม่
minikube delete
ผล
🔥 Deleting “minikube” in hyperkit …
💀 Removed all traces of the “minikube” cluster.
minikube start --driver=docker
ผล
🔥 Creating docker container (CPUs=2, Memory=1987MB) …
🐳 Preparing Kubernetes v1.20.2 on Docker 20.10.2 …
▪ Generating certificates and keys …
▪ Booting up control plane …
▪ Configuring RBAC rules …
🔎 Verifying Kubernetes components…
🌟 Enabled addons: storage-provisioner, default-storageclass
🏄 Done! kubectl is now configured to use “minikube” cluster and “default” namespace by default
เอาล่ะก่อนจะไปต่อ ผมอยากอธิบายภาพนี้
มือใหม่ควรทราบว่าเมื่อเราได้ติดตั้ง Docker Desktop และ Minikube ไว้ในเครื่องเรา (Local) แล้ว ทั้ง Docker Desktop และ Minikube จะมี Docker Daemon เป็นของตัวเอง (ไม่ยุ่งเกี่ยวกัน)
สังเกตตอนทำคำสั่งนี้ minikube start
มันเขียนว่า preparing Kubernetes and Docker นั่นหมายความว่ามันมี Docker (Daemon) เป็นของตัวเองแยกจาก Docker Desktop
จากภาพข้างต้นถ้าต้องการให้ image ที่เกิดจากคำสั่ง docker build
ใช้งานได้ที่ฝั่ง Minikube (โดยคำสั่ง kubctl run
) เราจะต้อง push image นั้นไปไว้บน Docker Hub เพราะ Minikube จะ pull image ด้วย Docker Daemon ของมันเอง
*** รายระเอียดของ Kubernetes จากบทความที่ผมเคยได้เขียนไว้ ที่นี่
จากภาพขออธิบายความสำคัญของ Docker Daemon อีกนิด
- มันคือ engine หรือ server ตัวหนึ่งที่เอาไว้ building, running และ distributing Docker containers
- มันรับคำสั่งจาก Docker Client ซึ่งดั่งเดิมเป็นโปรแกรมแบบ command line ที่รับคำสั่งจากผู้ใช้งานอีกที เช่น docker build, docker pull, docker run
- มันไม่สามารถรันใน OS อื่นใดนอกจาก Linux ดังนั้นทั้ง Windows และ macOS (และ OS อื่นๆที่ไม่ใช่ Linux) จะต้องสร้าง VM ขึ้นมาก่อนจึงจะสามารถใช้งาน Docker Daemon ได้ ฉะนั้นไม่ต้องแปลกใจที่คำสั่ง
minikube start --driver=docker
สามารถเลือก VM เองได้ก็เพื่อการนี้ ดูเพิ่ม ที่นี่ - มันมีชีวิตอยู่ใน Docker Host (โปรแกรม Docker) และใน Docker Host นี้ยังประกอบไปด้วย Images และ Containers
Let’s go
สร้างโปรเจกต์ Angular
ng new todo-list
ติดตั้ง Bootstrap 5
npm install bootstrap@5
angular.json
ระบุ CSS
"./node_modules/bootstrap/dist/css/bootstrap.min.css"
หน้าตาชิ้นงานของเรา
app.module.ts
เพิ่ม ReactiveFormsModule
app.component.ts
taskControl = new FormControl('')
items: string[] = []add() {
this.items.push(this.taskControl.value.trim())
this.taskControl.setValue('')
}
app.component.html
<div class="container-fluid">
<div class="row d-flex justify-content-center">
<div class="col-12 col-sm-6 bg-dark bg-gradient text-light">
<div class="mb-3">
<label class="form-label">Task</label>
<input class="form-control" [formControl]="taskControl">
</div>
<div class="mb-3 d-flex">
<button class="btn btn-primary ms-auto" (click)="add()">ADD</button>
</div>
</div>
</div>
<div *ngFor="let item of items; let i = index" class="row mt-3 d-flex justify-content-center">
<div class="col-12 col-sm-6 bg-dark bg-gradient text-light">
<div class="py-3">{{i + 1}}. {{item}}</div>
</div>
</div>
</div>
package.json
แก้ไขเลข version เป็น 1.0.0
build.sh
สร้างไฟล์ชื่อ bulid.sh ไว้ที่ root project ไฟล์นี้เอาไว้รันคำสั่ง docker build
สำหรับ Winows อ่าน ที่นี่
#!/bin/bash
NAME=$(cat package.json | grep name | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[[:space:]]')
VERSION=$(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[[:space:]]')
IMAGE=$NAME:$VERSION
docker build -t $IMAGE .
หรือจะเขียนแค่นี้ก็ได้
#!/bin/bash
docker build -t todo-list:1.0.0 .
Dockerfile
สร้างไฟล์ชื่อ Dockerfile ไว้ที่ root project ไฟล์นี้ถูกเรียก ณ path ที่ระบุไว้ใน bulid.sh โดย default สัญลักษณ์ dot หมายถึง root project
FROM node:14.17.5-alpine as build-stage
WORKDIR /app
COPY package.json /app
RUN npm install
COPY ./ /app
RUN npm run build -- --output-path=out
FROM nginx:1.21.1
COPY --from=build-stage /app/out /usr/share/nginx/html
COPY --from=build-stage /app/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
ENTRYPOINT ["nginx", "-g", "daemon off;"]
nginx.conf
สร้างไฟล์ชื่อ nginx.conf ไว้ที่ root project ไฟล์นี้จะถูกเรียกโดยคำสั่งใน Dockerfile เพื่อกำหนดการ routing แก่ Nginx (ผมเลือกใช้ Nginx เป็น web server) ในกรณีที่เราติดต่อกับ backend ผ่าน RESTful APIs ดังนั้นสร้างเผื่อไว้ก่อน
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;location / {
try_files $uri$args $uri$args/ /index.html;
}
}
จากนั้นที่ root project รันไฟล์ build.sh
sh build.sh
ผล
ติด TypeScript ต้องมีเวอร์ชันระหว่าง 4.0.0 และ 4.2.0
อื่ม…เป็นเพราะอะไรกันแน่นะ
ทดลองลบ node_modules แล้วรันใหม่
ผล
ตรวจดู Docker Image
docker images
หรือดูผ่าน Docker Dashboard
ทดสอบรันด้วย Docker
docker run -p 9000:80 todo-list:1.0.0
ผล
เอาล่ะ ในส่วนของ Docker ถือว่าเสร็จสิ้น ทำงานได้ปกติ มาดูในส่วนของ Kubernetes ผ่าน Minikube ซึ่งเป็นเป้าหมายของเรากันครับ
สร้าง k8s folder ไว้ ณ root project แล้วเริ่มเขียน Kubernetes scripts
k8s/front-deployment.yml
Kubernetes Deployment นั้นจะเรียกใช้งาน image อ่านเนื้อหาเพิ่มเติม ที่นี่
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-list
labels:
app: todo-list
spec:
replicas: 1
selector:
matchLabels:
app: todo-list
template:
metadata:
labels:
app: todo-list
spec:
containers:
- name: todo-list
image: todo-list:1.0.0
ports:
- containerPort: 80
รัน front-deployment.yml
kubectl apply -f k8s/front-deployment.yml
ผล
pod ไม่สามารถทำงานได้ เนื่องจาก pull image มาไม่ได้ เพราะไม่ได้ pull จาก Docker Hub หรือกล่าวได้ว่า image ที่ได้ถูกทำงานด้วย Docker Daemon ของ Docker Desktop หากจะให้ deployment นี้ทำงานได้จำต้อง push image ไปไว้ที่ Docker Hub เสียก่อน ทั้งหมดนี้คือตามความรู้เท่าที่เรามี
แต่เราจะไม่ทำแบบนั้น…
เพื่อแชร์ image ที่ได้ระหว่าง Docker Desktop กับ Minikube โดยไม่พึ่ง Docker Hub จะต้องหาความรู้เพิ่ม
และก็ได้ว่า eval $(minikube docker-env)
คือคำตอบของเรา อ่านเพิ่มเติม ที่นี่
เขาบอกว่าเราสามารถใช้ Docker Daemon ที่อยู่ภายใน Minikube มา build image ได้โดยระบุคำสั่ง eval $(minikube docker-env)
ก่อน จากนั้นจึงสั่ง docker build
ตามปกติ
มาดวนกันเลยดีกว่า เปิด Terminal พิมพ์
eval $(minikube docker-env)
ลองคำสั่ง docker images
จะปรากฏ image ทั้งหมดภายใน Minikube
ดังนั้นรัน sh build.sh
อีกครั้ง
ผล
เกิดเป็นภาพนี้
ฮั่นแน่! รู้เลยใช่ไหมขั้นตอนถัดไปผมจะทำอะไร
Kubernetes นั้นพยายาม pull image เป็นระยะอยู่แล้ว ในเมื่อ image พร้อมแล้วใน Minikube มันจึง pull สำเร็จ ลองไปตรวจดูสิครับ
kubectl get pod
ผล
คราวนี้มาสร้าง service เพื่อเรียกใช้งานกัน ใน k8s folder สร้างไฟล์ชื่อ front-service.yml
k8s/front-service.yml
Kubernetes Service มีหลายประเภท ที่เราต้องการคือ NodePort อ่านเพิ่มเติม ที่นี่
apiVersion: v1
kind: Service
metadata:
name: todo-list
spec:
type: NodePort
selector:
app: todo-list
ports:
- port: 80
nodePort: 30000
รัน front-service.yml
kubectl apply -f k8s/front-service.yml
ผล kubectl get services
ดูที่ Kubernetes Dashboard ว่าแต่ละออบเจ็กต์สถานะเป็นอย่างไร อ่านเพิ่มเติม ที่นี่
minikube dashboard
ผล
ทดสอบเรียกใช้งาน todo-list โดยจะต้องเรียกผ่านชื่อของ service
minikube service todo-list
ผล
สรุปว่าภาระกิจของเราที่ต้องการเขียน Angular 12 ให้รันด้วย Kubernetes บน localhost (127.0.0.1) เป็นอันสำเร็จ!
เกล็ดความรู้อื่นๆที่น่าสนใจ
- ทุกครั้งที่เปิด Terminal ใหม่จะต้องรันคำสั่ง
eval $(minikube docker-env)
ก่อนเสมอถ้าต้องการใช้งาน Docker Daemon ของ Minikube - หากต้องการยกเลิกใช้งาน Docker Daemon ของ Minikube ใช้คำสั่ง
eval $(minikube docker-env --unset)
อ่านเพิ่มเติม ที่นี่