ความทรงจำปี 2023

ปี 2023 ที่ผ่านมา

เผลอแป๊ปเดียวก็ได้มาเขียนบันทึกความทรงจำปี 2023 แล้ว ตอนแรกว่าจะไม่เขียนละ แต่เดี๋ยวถ้าไม่เขียนจะลืมเรื่องราวต่างๆในปี 2023 ไป ซึ่งจริงๆตอนนี้ก็ลืมไปหลายเรื่องละ แล้วที่บอกว่าเวลามันเร็วจริงๆแล้วไม่เร็วหรอกครับเราแค่รู้สึกไปเอง

งาน

ปีนี้ถือว่าเป็นปีที่ว่างเปล่าสำหรับการทำงานอีกปี แถมจะว่างเปล่ากว่าปีที่แล้วด้วยเพราะปีนี้ไม่ได้ทำผลงานอะไรให้กับที่ทำงานเลย ไม่มีงานที่มาจากมือเราที่เอาขึ้น Production เลย งานที่ไปทำการ POC ก็ล้มเหลวเกือบทุกอัน เพราะทำแล้วไม่ได้ใช้บ้าง ทำแล้วมันไม่เหมาะกับเอาขึ้น Production บ้าง บางอันเราก็ไม่เก่งพอที่จะทำความเข้าใจมันได้แบบจริงๆจังๆ ซึ่งก็เหมือนเสียเวลางานไปเปล่าๆ

ตำแหน่งงานก็มีปรับเปลี่ยนไปอยู่ทีมใหม่ แถมไปๆมาๆปี 2024 จะต้องมาดูแลคนอีก ซึ่งเป็นเรื่องที่ไม่ค่อยอยากทำเท่าไหร่ แต่ตอนนี้ไม่เหลือคนที่จะช่วยงานนี้แล้วก็เลยลองช่วยดู ถ้ามันดีจนเขาหาคนมาแทนได้ก็กลับไปทำตำแหน่งเดิม แต่ถ้าไม่ดีก็แค่หยุดแล้วกลับไปทำตำแหน่งเดิมก็เท่านั้น

ส่วนปีนี้ที่แปลกหน่อยก็ได้ดูแลเด็กฝึกงาน ซึ่งพอได้ดูแลน้องๆแล้วก็รู้สึกว่าเราแก่เอาเสียมากๆแบบตามไม่ทัน Trend ที่น้องๆคุยกัน แถมเรื่องความรู้นี่เหมือนน้องๆรู้เยอะกว่าเราตอนเรียนอีก แบบถ้าเอามาฝึกอีกนิดนี่น่าจะกลายเป็น Programmer เทพๆได้แน่นอน

ความรัก

ปีนี้ตัดสินใจลองจีบคนใหม่ แต่พอจีบไปได้สักพักก็เริ่มรู้สึกว่าเราเข้าไม่ถึงเขาเลย แถมพอเริ่มๆจีบไปรู้สึกเหมือนถ้าพยายามไปต่อก็คงไม่ดีต่อตัวเองกับคนที่จีบด้วยเดี๋ยวเข้าหน้ากันไม่ติดก็เลยหยุดไป จริงๆก็ยังมีเรื่องที่อยากทำอยู่แต่ว่ารอถึงวันที่มีโอกาสและเหมาะสมกว่านี้ก่อนดีกว่า

จริงๆว่าจะหยุดเรื่องนี้ไว้แล้วแต่ดันมีโอกาสได้คุยกับผู้หญิงที่เคยจีบซึ่งรอบนี้เหมือนเขาจะยอมเปิดใจคุยกับเรามากขึ้น แต่ก็เหมือนจะผิดที่ผมเองที่ไม่รู้ความพอเหมาะพอควรกับการรักษาความสัมพันธ์ อยากจะคุยกับเขา อยากจะมีเวลาอยู่กับเขามากเกินไปจนทำให้เขาอึดอัด ยิ่งเขาเป็นคนที่อึดอัดแล้วไม่พูดตรงๆก็ยิ่งทำให้เขาไม่สบายใจ ซึ่งพอคิดๆดูแล้วถ้าเป็นแบบนี้ต่อไปก็คงทำให้เขาลำบากใจก็เลยหยุดมันไว้ตรงนั้นดีกว่า เก็บความทรงจำดีๆที่มีน้อยนิดนั้นไว้ดีกว่า

แต่ปีนี้ก็มีเรื่องที่ไม่รู้ดีไหมคือปีนี้ขอ IG สาวที่ชอบสำเร็จแล้ว ตอนนั้นจำได้ว่ากว่าจะพูดออกไปได้นี่ยากเหลือเกิน ในหัวมีแต่เขาจะว่าเราไหมนะ จากนี้จะมองหน้ากันได้เหรอ เขาจะมองเรายังไง แต่สุดท้ายเรื่องที่เราอ่านก็ย้อนเข้ามาในหัวว่า “มันมีเรื่องที่เราควบคุมไม่ได้ หน้าที่ของเราคือทำเรื่องที่เราควบคุมได้” ซึ่งเรื่องที่เราควบคุมได้คือ การเอ่ยปากขอ ส่วนเขาจะให้ไม่ให้ไม่ใช่เรื่องที่เราควบคุม ผลจะเป็นอย่างไรก็ปล่อยมันไป แค่เราทำสิ่งที่เราทำได้เต็มที่ก็พอ ซึ่งพอทำไปแล้วก็สบายใจเพราะเหมือนได้เข้าใกล้คนที่ตัวเองอยากเป็นไปอีกขั้น แถมได้ IG ของคนที่ชอบมาด้วย ฮ่าๆๆๆๆๆ

ความตาย

ปีนี้ได้ใกล้ชิดกับความตายมากขึ้นอะไรหลายๆอย่างที่คุ้นเคยเห็นกันมานานเป็น 10 - 20 ปีอยู่ดีๆก็หายไป คนที่รักก็เจ็บป่วยจนเกือบจะจากกันแบบไม่ได้จากลา ไม่ได้เตรียมตัว ตอนนั้นนี่แบบนี่มันอะไรวะ เกิดอะไรขึ้น แล้วเราจะต้องทำยังไง ซึ่งจริงๆมันเป็นเรื่องที่เรากลัวที่สุดเพราะเรารู้ดีว่าเราไม่ใช่ผู้ใหญ่ เราเป็นแค่เด็กอายุ 12 ที่อยู่ในร่างคนอายุ 30 เราไม่เคยรับผิดชอบอะไรจริงๆจังๆ ไม่เคยตัดสินใจอะไรใหญ่ๆ สุดท้ายทุกอย่างก็ผ่านไป ได้บทเรียนหลายๆอย่างจากเรื่องนี้ไม่ว่าจะเวลาที่เหลือน้อยลงทุกที อะไรที่ยังขาด ได้รู้จักความกลัวของตัวเองไปอีกขั้น ซึ่งก็ยังไม่รู้วิธีจัดการ แต่อย่างน้อยเราก็รู้ว่าเรากลัวอะไรล่ะนะ

อ่านหนังสือ

ปีนี้ยังอ่านหนังสืออยู่ถึงไม่เยอะเท่าเดิมแต่ทุกครั้งที่อ่านรู้สึกว่าสนุกและรู้สึกผ่อนคลาย เหมือนเราได้ไปท่องเที่ยวในโลกกว้างโดยไม่ต้องเชื่อมต่อกับใคร เราได้อยู่กับตัวอักษรได้ทำความเข้าใจข้อความมากมายที่อยู่ในนั้น ทำให้เราตัดขาดจากโลกอันแสนวุ่นวายที่เราพยายามเอาตัวเข้าไปเกี่ยว ทั้งๆที่เราไม่จำเป็นต้องเข้าไปยุ่ง แล้วพอไปยุ่งก็มีแต่สร้างความเดือดเนื้อร้อนใจต้องไปทุกข์กับเรื่องของคนอื่น

ปรัชญา

ปีนี้ได้เข้าใจปรัชญามากขึ้นได้เข้าใจจุดประสงค์ของมันหลังจากหลงทางไขว่คว้าหาความรู้จนลืมเป้าหมายของมัน ตอนนี้พอเข้าใจเป้าหมายของมันแล้วเวลาอ่านปรัชญาแล้วรู้สึกสนุกขึ้น ได้คิดตาม ได้พยายามเข้าถึงเป้าหมายก็เป็นอะไรที่สนุกดี ถึงบางทีจะยากแต่ก็สนุกที่จะลองทำ

ละครเวที

ปีนี้ไปดูละครเวทีบ่อยขึ้น บ่อยแบบลางานไปดูเลยก็มี จะว่าไปก็เริ่มชอบการดูละครเวทีนะ การได้ดูอะไรที่ย้อนไม่ได้ ต้องตั้งใจ จดจ่อ คิดไปด้วยลุ้นไปด้วย เป็นอะไรที่สนุกดี แถมละครเวทีหลายๆเรื่องที่ไปดูก็มีแง่คิด มุมมอง มีอะไรที่น่าสนใจกว่าหนังหรือละครกระแสหลักที่คนชอบกัน ทำให้ยิ่งดูยิ่งสนุกจนถึงขนาดยอมเดินทางไปที่ที่ไม่เคยไปเลย

งานเขียน

ปีนี้ได้ลองเขียนงานลง web ดู เป็นเรื่องสั้นที่ดูขัดแย้งกับกระแสหลัก แถมดูจะขัดกับความชอบของคนส่วนใหญ่ด้วย ซึ่งผลออกมาก็ไม่ค่อยดีสักเท่าไหร่ แต่อย่างน้อยก็ได้เขียนได้ระบายความคิดของตัวเองให้คนอื่นอ่าน อย่างน้อยเราก็มีงานประมาณ 100 หน้า (เป็นหนังสือเล่มเล็กๆได้เลยนะเนี่ย) ของตัวเองแล้ว

ใครสนใจไปอ่านได้ที่ : https://www.readawrite.com/a/999058dbffdbc084ac18e535e303ce52

ดูหนัง

เนื่องจากเฮิร์ทเพราะจีบสาวไม่ติด 2 รอบติดต่อกัน จนมาถึงจุดที่ว่าต้องหาอะไรทำเพื่อพาตัวเองออกจากวังวนที่คิดเรื่องนั้นซ้ำไปซ้ำมา จนสุดท้ายมาเห็นว่า Major มีสมาชิกรายเดือน และตอนนั้นมีหนังที่อยากดูหลายเรื่องก็เลยสมัครแล้วไปดูหนัง 4 เรื่องติดต่อกัน ซึ่งพอได้ดูหนังแล้วก็สนุกดี เหมือนตัวเองหลุดไปจากโลกที่กำลังอยู่ ได้ไปทำความเข้าใจหนัง พยายามค้นหาว่าหนังบอกอะไร ได้ดูทั้งหนังดี หนังไม่ดี ได้เห็นลูกเล่นการเล่าเรื่อง การสร้างตัวละคร ได้หงุดหงิดกับความอะไรวะของบท พอดูบ่อยก็ติดเป็นนิสัยจนตอนนี้กลายเป็นว่าปีนี้ทั้งปีดูหนังเกือบจะเยอะกว่าทั้งชีวิตอีก

ช่อง Youtube

ปีนี้ได้ทำช่อง Youtube เยอะขึ้น จากตอนแรกว่าจะลงเกี่ยวกับการเขียน Code อย่างเดียว แต่คิดไปคิดมาไหนๆเราก็มีอะไรอยากเล่า อยากพูดมากกว่าเรื่อง code แถมเรื่อง code ก็พูดไปเกือบหมดแล้ว ก็เลยเอาวะทำ podcast พูดเรื่องหนังกับละครเวทีที่ดู หนังสือที่อ่าน แล้วก็มีทำคลิปสอนเรื่องเกี่ยวกับการเขียนโปรแกรม ซึ่งทำไปก็สนุกดีนะ แบบมันมีอะไรให้เราทำอยู่ตลอด

ใครสนใจอยากฟังเกี่ยวกับหนัง หนังสือ หรือ เรื่องเกี่ยวกับการเขียนโปรแกรมไปกดติดตามได้ที่ : https://www.youtube.com/channel/UCd1p76uP-ufG8UEWuuTWzUg

จัดฟัน

จริงๆเลิกคิดจะจัดฟันไปนานแล้วเนื่องจากคิดว่าฟันก็ปกติดีไม่มีปัญหาอะไรแล้วก็อยู่ได้มาตั้ง 30 ปี ก็ไม่น่าจะต้องปรับแต่งอะไร แต่อยู่ดีๆก็เริ่มเจ็บฟันคุดขึ้นมาก็เลยคิดว่าเออไปถอนฟันคุดทิ้งดีไหม พอดีกับคนที่ทำงานชวนไปลองดูว่าถ้าจัดฟันต้องทำอะไรบ้าง ตอนแรกก็กะจะไม่ทำอยู่ดี จนผู้หญิงที่ผมชอบ (แต่เขาแต่งงานไปแล้ว)บอกว่า ทำไมแกเห็นแก่ตัวอย่างนี้นะ แกไม่สงสารคนที่รักแกในอนาคตเลยเหรอที่เขาจะต้องมาทนกับหน้าแกแบบนี้ไปทั้งชีวิต แกเหมือนเป็นคนเห็นใจคนอื่นรักคนอื่นนะ แต่ไม่ใช่อะแกเห็นแก่ตัว พอผมได้ฟังประโยคนี้นี่ทำให้ได้คิดในมุมที่ไม่เคยคิด พอมานึกภาพผู้หญิงที่ผมรักต้องมาทนกับหน้าตาของผู้ชายคนหนึ่งที่ออกไปทางไม่หล่อตามค่านิยมกลาง ถึงเขาจะไม่คิดอะไร แต่ถ้าเราดูดีกว่านี้ ดูปกติอยู่ในค่านิยมกลางสักหน่อย เขาคงจะมีความสุขขึ้นสักนิดล่ะนะ พอคิดได้แบบนั้นก็เลย เอาวะลองดู ฟันเหยินมา 20 กว่าปีละ ลองเป็นคนฟันไม่เหยินในช่วงชีวิตที่เหลือดู อย่างน้อยก็เพื่อคนที่เรารัก (ที่ไม่รู้ว่ามีหรือเปล่าดู)

ปี 2024 ทำอะไรดีล่ะ

  • ปลูกต้นไม้

    หลังจากคิดหาอะไรทำมานานก็มาคิดถึงอะไรที่ทำแล้วไม่ต้องคอยดูตลอด แต่ต้องไปดูแลบ้าง คิดไปคิดมาก็ซึ่งสิ่งนั้นก็คือการปลูกต้นไม้ เพราะเราไม่ต้องคอยเฝ้าจับตามันตลอดเวลามันก็สามารถเจริญเติบโตเองได้ แต่เราก็ต้องไปดูแลมันบ้าง ซึ่งถือเป็นงานอดิเรกที่ไม่แน่นและไม่หลวมจนเกินไป

  • ออกกำลังกาย

    จริงๆปีนี้ก็ออกกำลังกายนะ แต่ออกบ้างไม่ออกบ้างไม่มีวินัยในตัวเองเท่าไหร่ ปีนี้จะพยายามมีวินัยให้มากกว่านี้ กินอาหารตามหลักการให้จริงจังมากขึ้น ร่างกายจะได้แข็งแรงขึ้น

  • ทำช่อง Youtube ต่อ

    ปีนี้ก็จะทำช่อง Youtube ต่อโดยจะทำเรื่องเกี่ยวกับเรื่อง code ให้เยอะขึ้นเพราะตอนนี้ช่องจะกลายเป็นช่อง Review หนังไปแล้ว

ปีนี้ได้ผ่านไปแล้ว เราไม่สามารถย้อนกลับไปได้แล้วเหลือเป็นเพียงความทรงจำไว้ระลึกถึงในตอนแก่เฒ่า เราทำได้เพียงอยู่กับปัจจุบันใช้ทุกวินาทีที่ย้อนกลับไม่ได้ทำในสิ่งที่เราต้องการ แล้วเจอกันใหม่ในปีหน้าตอนที่กลับมาอ่าน

Nobody knows

เพลงที่ได้ฟังในปีนี้

ยื้อ
ร้องไปกับฟ้า
หมากับเงา
คนขี้อิจฉา
ฤดูหนาว
จะหมัดจะมวย
ทนได้ทุกที
วาระเมิดใจ
กอดเสาเถียง
ขอบใจเด้อ
พลังรัก
ซังได้ซังแล้ว
อยู่ข้างหางตา
บ่ต้องอ้อนวอน
ฉันยังเก็บไว้
ภาพฝัน
แค่บอกว่ารักเธอ
หลงกล
ทำไมเป็นคนแบบนี้
ถามใจ
ปิดทองหลังใจ
พรที่ดี
ง่ายเกินไป
เร้น
นางฟ้ากับควาย
แค่ได้คิดถึง
ยิ่งใกล้ยิ่งเจ็บ
ซังได้ซังแล้ว
ความรักทำให้คนตาบอด
หากันจนเจอ
หนักใจ

Basic Kubernetes Part 10 - Helm - เครื่องมือที่ช่วยในการ Deploy อะไรที่ซับซ้อน Part 3

Basic Kubernetes Part 10 - Helm - เครื่องมือที่ช่วยในการ Deploy อะไรที่ซับซ้อน Part 3

ในการ Deploy นั้นไม่ใช่ว่าทุกการ Deploy จะสำเร็จ บางทีอาจจะเกิดปัญหาขึ้น เช่น มีการ config ผิด หรือพอ Deploy ไปแล้วเกิด Bug ที่รุนแรงจนระบบไม่สามารถทำงานต่อได้ ซึ่งเมื่อเกิดเรื่องแบบนี้ขึ้นก็เป็นที่แน่นอนว่าเราต้องทำการ Rollback เอาการ Deploy ครั้งก่อนกลับมา ซึ่งวิธีการ Rollback กลับมาก็มีได้หลายวิธี เช่น เอา VM ที่ Backup ไว้กลับมา เอา Folder ของ App เวอร์ชั่นเก่ากลับมา เป็นต้น

คราวนี้พอเรามาใช้ k8s ในการ deploy application เราจะเอาการ deploy เก่ากลับมายังไง วิธีง่ายสุดก็คือเก็บไฟล์ yaml เก่าเก็บไว้ พอมีปัญหาค่อยเอาตัวเก่ามาสั่ง apply แต่ปัญหาคือ ในไฟล์ yaml ที่เราสร้างนั้นมีการเพิ่ม deployment service หรือสิ่งต่างๆเข้าไป พอคุณเอาไฟล์เก่ามา apply ตัว resource ใหม่ที่เพิ่มมามันไม่หายไปด้วย ซึ่งนั่นเป็นปัญหาที่คุณต้องตามไปจัดการ

ซึ่งคนที่ใช้ helm ในการ deploy ก็เจอปัญหานี้เหมือนกัน ดังนั้นเขาจึงคิดวิธีการจัดการปัญหาโดยมี Feature การ Rollback ให้กับเราครับ

Deploy version 1

ตัวอย่างไฟล์ทั้งหมดสามารถดูได้ที่ Link

ในขั้นแรกเราลอง Deploy application version 1 กันก่อนโดยไฟล์ template ที่ใช้ในการ deploy คือ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami-deployment
labels:
app: whoami
spec:
selector:
matchLabels:
app: whoami-app
template:
metadata:
labels:
app: whoami-app
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
env:
- name: "OWNER"
value: {{ .Values.owner }}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami-ingress
spec:
rules:
- host: {{ .Values.domain }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami-service
port:
number: 80
---
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
selector:
app: whoami-app
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP

จากนั้นสั่ง deploy

1
helm upgrade --install whoami-helm --namespace whoami-namespace  --create-namespace whoami/

จากนั้นลองสั่งคำสั่งด้านล่างเพื่อดู Resource ที่เราทำการสร้างขึ้นมาว่ามีอะไรบ้าง

1
kubectl -n whoami-namespace get all

จากนั้นลองสั่งคำสั่งด้านล่างเพื่อดูรายละเอียดของชุด Deploy ที่เราสร้างด้วย helm ครับ

1
helm list -A

จะเห็นว่ามีการสร้างชุด deploy ชื่อ whoami-helm ขึ้นมาแล้วและเลข REVISION เป็น 1 ให้จำเลขตัวนี้ไว้นะครับ

ลองเข้า http://company-c.com/?env=true ผ่าน browser ซึ่งจะเห็นว่าสามารถเปิดได้

Deploy version ที่มีปัญหา

คราวนี้เรามาลองจำลองสถานการณ์ว่าเรา Deploy แล้วเกิดปัญหา โดยผมจะทำการแก้ config ให้ผิด และ เพิ่ม deployment ที่ไม่ใช้งานเข้าไป โดยดูตัวอย่างได้ใน folder : whoami-bug

แก้ไขไฟล์ values.yaml

จากไฟล์จะเห็นว่าผมแก้ตัว domain เปลี่ยนไปทำให้เข้าด้วย http://company-c.com/?env=true ไม่ได้ อีกทั้งก็เปลี่ยนค่า environment owner ไป

1
2
domain: "wasinee-company-c.com"
owner: "WASINEE BD"

แก้ไขไฟล์ whoami.yaml

จากไฟล์ผมจะทำการเพิ่มตัว nginx-bug-deployment เข้าไปเพื่อให้มี deployment ใหม่ที่เกินเข้าไป

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami-deployment
labels:
app: whoami
spec:
selector:
matchLabels:
app: whoami-app
template:
metadata:
labels:
app: whoami-app
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
env:
- name: "OWNER"
value: {{ .Values.owner }}
---

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-bug-deployment
labels:
app: nginx-bug
spec:
selector:
matchLabels:
app: nginx-bug-app
template:
metadata:
labels:
app: nginx-bug-app
spec:
containers:
- name: nginx-bug-container
image: nginx:1.25.3-alpine
ports:
- containerPort: 80
---

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami-ingress
spec:
rules:
- host: {{ .Values.domain }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami-service
port:
number: 80
---
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
selector:
app: whoami-app
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP

จากนั้นสั่ง deploy ด้วยคำสั่งด้านล่างเพื่อ update ตัว helm

1
helm upgrade --install whoami-helm --namespace whoami-namespace  --create-namespace whoami-bug/

จากนั้นลองสั่งคำสั่งด้านล่างเพื่อดู Resource ที่เราทำการสร้างขึ้นมาว่ามีอะไรบ้าง

1
kubectl -n whoami-namespace get all

จากภาพจะเห็นว่ามี deployment nginx-bug-deployment โผล่ขึ้นมา

จากนั้นลองสั่งคำสั่งด้านล่างเพื่อดูรายละเอียดของชุด Deploy ที่เราสร้างด้วย helm ครับ

1
helm list -A

จะเห็นว่า whoami-helm เปลี่ยน REVISION เป็น 2 แล้ว

ลองเข้า http://company-c.com/?env=true ผ่าน browser ซึ่งจะเห็นว่าไม่สามารถเปิดได้

Rollback กลับเป็น version เก่า

จากตัวส่วนที่แล้วเราได้จำลองการ Deploy ที่เกิดปัญหาคือค่า config domain ผิดและทำการเพิ่ม deployment ใหม่เข้าไป คราวนี้ถ้าเราอยากให้การ deployment ของเราทั้งหมดกลับไปเป็น version ก่อนหน้านี้คือ มีค่า config domain เดิม และ deployment เหมือนเดิมทั้งหมดจะทำยังไงดี อย่างที่บอกไป helm มีคำสั่งให้เรา rollback อยู่แล้ว ซึ่งเราสามารถสั่งได้โดยสั่ง (เดี๋ยวเรามาอธิบายคำสั่งทีหลัง)

1
helm rollback -n whoami-namespace whoami-helm 1 --force

เมื่อสั่งเสร็จเรามาลอง get resource ดูกันว่าเกิดอะไรขึ้น

1
kubectl -n whoami-namespace get all

จะเห็นว่าตัว nginx-bug-deployment หายไปแล้ว

ลองเข้า http://company-c.com/?env=true ผ่าน browser ซึ่งจะเห็นว่าสามารถเปิดเข้าไปได้เหมือนเดิมแล้ว

ซึ่งนั่นแปลว่าการ Deploy ทั้งหมดกลับมาเป็น Version 1 ที่เราต้องการแล้ว ซึ่งแปลว่าเราสามารถทำการ Rollback สำเร็จแล้ว

คราวนี้เรามาลอง helm list ดูกันว่ามีอะไรเปลี่ยนแปลง

1
helm list -A

จากภาพจะเห็นว่าเลข REVISION เปลี่ยนเป็น 3 ต่อจาก 2

อธิบายคำสั่ง helm rollback

1
helm rollback -n whoami-namespace whoami-helm 1 --force
  • helm rollback : เป็นคำสั่งเพื่อสั่งให้ทำการ Rollback
  • -n whoami-namespace : -n เป็นตัวบอกว่าตัวชุด deploy ที่ต้องการ rollack นั้นอยู่ที่ไหน โดยจากตัวอย่างคือ whoami-namespace
  • whoami-helm : ตรงส่วนนี้คือชื่อตัวชุด deploy โดยในตัวอย่างคือ whoami-helm
  • 1 : ตรงส่วนนี้คือตัว REVISION ที่เราต้องการ Rollback กลับไปหา ในที่นี้คือ 1
  • –force : ส่วนนี้เป็นการบอกว่าให้ทำการ update ไม่ว่าจะต้องทำการลบ หรือ สร้างใหม่ก็ตาม

รวมแล้วคือ ทำการ rollback ตัวชุด deploy : whoami-helm ที่อยู่ที่ namespace : whoami-namespace ให้กลับไปอยู่ใน REVISION ที่ 1 โดยทำการลบหรือสร้างใหม่ถ้าจำเป็นต้องทำ

ซึ่งเมื่อทำการสั่ง rollback ไปแล้วนั้นเลข REVISION นั้นจะไม่ได้กลับไปเป็นเลข 1 นะครับ ตัวเลข REVISION จะเพิ่มขึ้นเรื่อยๆ แต่ค่าการ config ต่างๆของ RESOURCE นั้นจะกลับไปเท่ากับ REVISION ที่เราเลือกในคำสั่งครับ

รายละเอียดเพิ่มเติมเกี่ยวกับคำสั่งสามารถดูได้ที่ : https://helm.sh/docs/helm/helm_rollback/

สรุป

ในตอนนี้เราได้รู้วิธีการ Rollback การ Deploy โดยใช้ helm แล้ว ซึ่งง่ายมากๆแค่ใช้คำสั่งคำสั่งเดียว และสำหรับตอนนี้ถือเป็นตอนสุดท้ายของ Basic Kubernetes ซึ่งผมก็หวังว่าทั้ง 10 ตอนที่ได้เขียนไปจะมีประโยชน์กับผู้อ่านได้นำไปใช้ในการทำงาน แล้วเจอกันใหม่ในเรื่องหน้าสวัสดีครับ

Basic Kubernetes Part 9 - Helm - เครื่องมือที่ช่วยในการ Deploy อะไรที่ซับซ้อน Part 2

Basic Kubernetes Part 9 - Helm - เครื่องมือที่ช่วยในการ Deploy อะไรที่ซับซ้อน Part 2

ในตอนที่แล้วเราได้ลองใช้งานตัว Helm ในการทำการ deploy application โดยทำการสร้าง template และแทนค่าด้วย value ตามที่กำหนดไว้ ซึ่งเราสามารถ deploy application ให้กับ company a , company b , company c ผ่านการใช้ config file ซึ่งดูเหมือนว่ามันจะหมดแล้ว แต่ในการใช้งานจริงนั้นมีความต้องการอีกหลายอย่างที่ต้องการและ Helm สามารถช่วยเราได้

ตัวอย่างไฟล์ทั้งหมดสามารถดูได้ที่ Link

if

ในบางกรณีที่เราต้องการ Deploy ที่ต้องใช้ if ตัวอย่างเช่นถ้าเป็นลูกค้า vip นั้นจะใช้ memory ได้ 500MB แต่ถ้าไม่ใช่จะใช้ memory ได้แค่ 100MB (ผมขอใช้ env บอกขนาด memory ที่ใช้ได้ แทนการบังคับ memory จริงๆ จะได้ตรวจสอบง่ายๆ)

ด้วยความรู้ที่เรามีตอนนี้สิ่งที่เราทำได้คือการสร้างค่า config เพิ่มในไฟล์ values.yaml และแก้ไขไฟล์ template ดังตัวอย่างด้านล่าง

values.yaml

1
2
3
domain: "company-c.com"
owner: "NORMAL PROGRAMMER"
maxMemory: "400MB"

จากตัวอย่างด้านบนเราทำการสร้าง config ชื่อ maxMemory ขึ้นมาเพื่อใช้แสดง memory สูงสุดที่สามารถใช้ได้

whoami.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami-deployment
labels:
app: whoami
spec:
selector:
matchLabels:
app: whoami-app
template:
metadata:
labels:
app: whoami-app
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
env:
- name: "OWNER"
value: {{ .Values.owner }}
- name: "MAX_MEMORY"
value: {{ .Values.maxMemory }}
---
# ผมขอละการประกาศส่วน service กับ ingress นะครับ

จากตัวอย่างด้านบนจะเห็นว่าเราเพิ่ม .Values.maxMemory เข้าไป ด้วยการทำเท่านี้เราก็สามารถ deploy application ได้ตามความต้องการแล้ว

ปัญหาที่เกิดขึ้น

ปัญหาที่เกิดขึ้นนั้นมาจากการเรากำหนด maxMemory ไว้ที่ไฟล์ config เพื่อบอกว่า memory ที่ใช้ได้คือเท่าไหร่ ทีนี้ลองคิดภาพตามว่าถ้าสมมติว่ามีการเปลี่ยนกฏเกี่ยวกับ maxMemory เช่น ลูกค้า VIP ใช้ maxMemory ได้ 800MB ส่วนที่ไม่ใช่จะได้ 600MB ถ้ามีการเปลี่ยนกฏแบบนี้รับรองเลยว่าคุณจะต้องไปไล่แก้ config ไฟล์ของทุกลูกค้าโดยแก้จาก 400MB ไปเป็น 800MB ซึ่งแน่นอนว่าถ้ามีสัก 100 บริษัทก็น่าจะเป็นฝันร้ายในการไล่แก้ อีกทั้ง ถ้าชนิดของลูกค้ามีหลายระดับเช่น nomal , goal , vip แล้วบางระดับใช้ memory เท่ากัน เช่น goal กับ vip ใช้ memory เท่านั้น คราวนี้คุณจะไม่สามารถ replace all 400MB ไปเป็น 800MB ได้แล้ว เพราะคุณต้องไปแยกเองว่าตกลงแล้วบริษัทนี้มันอยู่ระดับอะไร สร้างความวุ่นวาย ปวดหัวไปอีก นี่ยังไม่รวมว่าถ้าย้ายระดับจะต้องแก้กี่ค่า (กรณีที่ไม่ได้จำกัดแค่ maxMemory)

ใช้ if มาแก้ปัญหา

จากปัญหาในส่วนที่แล้ว จะเห็นว่าเราต้องการควบคุมขนาดของ maxMemory ที่เดียว ไม่อยากไปจัดการที่ไฟล์ config ดังนั้นเราจึงควรกำหนดมันลงไปที่ template เลย ในส่วน config นั้นเราเปลี่ยนมากำหนดชนิดของลูกค้าไปในไฟล์ values แทน เพื่อที่จะได้รู้ไปเลยว่าตกลงแล้วลูกค้านี้เนี่ยอยู่ในระดับใด และเวลาแก้ไขก็แก้ระดับก็แก้ทีค่าเดียวไปเลย

values.yaml

1
2
3
domain: "company-c.com"
owner: "NORMAL PROGRAMMER"
customerType : "VIP"

ในส่วนนี้เราจะเพิ่ม config value : customerType ขึ้นมาเพื่อบอกว่าลูกค้านี้อยู่ type อะไร

whoami.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami-deployment
labels:
app: whoami
spec:
selector:
matchLabels:
app: whoami-app
template:
metadata:
labels:
app: whoami-app
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
env:
- name: "OWNER"
value: {{ .Values.owner }}
- name: "CUSTOMER_TYPE"
value: {{ .Values.customerType }}
- name: "MAX_MEMORY"
{{ if eq .Values.customerType "VIP" }}
value: "400MB"
{{ else if eq .Values.customerType "GOLD" }}
value: "300MB"
{{ else }}
value: "100MB"
{{ end }}
---
# ผมขอละการประกาศส่วน service กับ ingress นะครับ

จากตัวอย่างจะเห็นว่าเราทำการเพิ่มส่วน if else เข้าไป โดยตรง if eq .Values.customerType “VIP” เป็นเงื่อนไขที่ต้องการ ถ้าตรงจะให้ทำสิ่งนั้น ในที่นี้เราทำการตั้งเงื่อนไขว่า ถ้า customerType มีค่าเป็น VIP เราจะกำหนด value ของ maxMemory เป็น 400MB ในส่วนของ else if ก็ใช้หลักการเดียวกันแต่เปลี่ยนจาก VIP เป็น GOLD และส่วนที่เป็น else ก็คือถ้าไม่ตรงอะไรเลยก็เข้าเงื่อนไขนั้นซึ่งในที่นี้คือกำหนด memory เป็น 100MB

ทดสอบ Deploy

เมื่อเราลอง dpeloy และเข้าดูผ่าน browser ที่ url : http://company-c.com/?env=true ซึ่งเมื่อเข้าไปดูแล้วจะพบว่า maxMemory เป็น 400MB ตามเงื่อนไขที่เรากำหนด

คราวนี้เรามาลองเปลี่ยน config customerType ในไฟล์ values.yaml ดูว่าถ้าเปลี่ยนแล้วค่าจะเปลี่ยนตามไหม โดยผมจะลองเปลี่ยน custmoerType เป็น GOLD ซึ่งเมื่อเปลี่ยนแล้วลองไปเช็คดูจะพบว่าค่าเปลี่ยนเป็น 300MB ตามที่ตั้งค่าไว้

คราวนี้ลองเปลี่ยน customerType เป็น NORMAL และลอง deploy update จะพบว่าค่าเปลี่ยนเป็น 100MB

ซึ่งจากการทดลองจะเห็นว่าเราสามารถ if else ได้ตามความต้องการของเราแล้ว

loop

ในบางกรณีเราอยากจะวน loop ทำอะไรสักอย่าง ตัวอย่างเช่น ถ้าลูกค้าบางคนต้องการเพิ่ม environment เพิ่มเข้าไปเองเพื่อเอาไปใช้ ซึ่งแต่ละลูกค้ามี environment ที่ไม่เหมือนกันเลยนั้น เราจะทำอย่างไร ซึ่งด้วยความรู้ที่เรามีอยู่ตอนนี้เราไม่สามารถทำได้เลยเพราะ template นั้นเป็นค่า fix จะไปรู้ได้อย่างไรว่ามีค่า environment อะไรบ้าง มีกี่ตัว ตรงจุดนี้เองที่ loop จะเข้ามาช่วยเรา

values.yaml

1
2
3
4
5
6
7
8
domain: "company-c.com"
owner: "NORMAL PROGRAMMER"

customEnvironment:
CUSTOM_VALUE_1 : "WASINEE"
MY_TIMEZONE: "Asia/Bangkok"
MAX_RETRY: "5"
USERNAME: "TEST_USER"

ตรงส่วนนี้เราทำการเพิ่ม config : customEnvironment โดยภายใต้ customEnvironment นั้นเรามีการกำหนด environment ที่เราจะเพิ่มเข้าไปคือ CUSTOM_VALUE_1 , MY_TIMEZONE , MAX_RETRY, USERNAME

whoami.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami-deployment
labels:
app: whoami
spec:
selector:
matchLabels:
app: whoami-app
template:
metadata:
labels:
app: whoami-app
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
env:
- name: "OWNER"
value: {{ .Values.owner }}
{{ range $key, $value := .Values.customEnvironment }}
- name: "{{ $key }}"
value: "{{ $value }}"
{{ end }}
---
# ผมขอละการประกาศส่วน service กับ ingress นะครับ

ตรงส่วนนี้เราจะเพิ่มส่วนการ for loop เอาค่าไปใน environment โดยตรงส่วนที่เราประกาศว่า range $key, $value := .Values.customEnvironment ตรงส่วนนี้คือการบอกว่าจะทำการวน loop โดยวนที่ตัว customEnvironment ซึ่งภายในมีค่า

1
2
3
4
CUSTOM_VALUE_1 : "WASINEE"
MY_TIMEZONE: "Asia/Bangkok"
MAX_RETRY: "5"
USERNAME: "TEST_USER"

โดยตอนวนนั้นจะให้ค่า $key คือค่าที่เป็น key (ซึ่งนั่นก็คือ CUSTOM_VALUE_1 , MY_TIMEZONE , MAX_RETRY , USERNAME ) และค่า $value คือค่าที่เป็น value (ซึ่งนั่นก็คือ WASINEE , Asia/Bangkok , 5, TEST_USER) ดังนั้นเวลาวนค่าของ key value จะเป็น

1
2
3
4
- key : CUSTOM_VALUE_1  - value : WASINEE
- key : MY_TIMEZONE - value : Asia/Bangkok
- key : MAX_RETRY - value : 5
- key : USERNAME - value : TEST_USER

ซึ่งในส่วนที่ต่อมาคือส่วนที่เราเอาค่า key value ไปใช้ ในที่นี้คือการกำหนดค่า environment ของ pod นั่นเอง

ทดสอบ Deploy

เมื่อเราลอง dpeloy และเข้าดูผ่าน browser ที่ url : http://company-c.com/?env=true จะพบว่ามีค่า Environment เป็นไปตามที่เราใช้ loop กำหนดใน template

คราวนี้เรามาลองทดสอบเพิ่มค่าใน customEnvironment เหมือนด้านล่าง จากนั้นลองสั่ง Deploy

1
2
3
4
5
6
7
8
9
10
domain: "company-c.com"
owner: "NORMAL PROGRAMMER"

customEnvironment:
CUSTOM_VALUE_1 : "WASINEE"
CUSTOM_VALUE_2 : "PICHAYA"
CUSTOM_VALUE_3 : "SUNISA"
MY_TIMEZONE: "Asia/Bangkok"
MAX_RETRY: "5"
USERNAME: "TEST_USER"

ซึ่งจากภาพจะเห็นว่าค่า environment มีการเพิ่มตามที่เราเพิ่มในไฟล์ values.yaml จริงๆ ดังนั้นก็แปลว่าการประกาศ template แบบ loop ที่เราต้องการนั้นทำงานถูกต้อง

สรุป

สำหรับตอนนี้เราได้เรียนรู้การใช้ความสามารถของ Helm ในการใช้ if และ loop บนตัว template เพื่อให้สามารถทำการ deploy application ที่มีความซับซ้อนมากขึ้นได้แล้ว สำหรับตอนหน้าซึ่งน่าจะเป็นตอนสุดท้ายของซีรีย์ Basic Kubernetes เราจะมาเรียนรู้วิธีการ Rollback โดยใช้ Helm

Basic Kubernetes Part 8 - Helm - เครื่องมือที่ช่วยในการ Deploy อะไรที่ซับซ้อน Part 1

Basic Kubernetes Part 8 - Helm - เครื่องมือที่ช่วยในการ Deploy อะไรที่ซับซ้อน Part 1

ในตอนที่แล้วเราได้ลองทำโจทย์การ Deploy : WordPress ทำให้เราเห็นว่ามันมีความต้องการที่ต้องการ Deploy WordPress ที่มี Template เดียวกัน แตกต่างกันแค่ Config เช่น domain name ซึ่งในตอนที่แล้วเราใช้ envsubst ในการทำ แต่ปัญหาที่เรายังแก้ไม่ได้ก็คือ ถ้ามีการแบ่งลูกค้าเป็นหลายแบบ แต่ละแบบมี template ที่ deploy แตกต่างกันเพียงเล็กน้อยเราจะทำอย่างไร (จริงๆก็แยกเป็นหลายๆ template เลยก็ได้นะ แต่มันดูเหนื่อยเกินไปถ้ามีสัก 5 แบบ แล้วแตกต่างกันนิดเดียว) ซึ่งปัญหานี้ได้มีคนสร้างเครื่องมือเพื่อแก้ปัญหาแล้ว เครื่องมือนั้นคือ Helm ที่เรากำลังจะไปเรียนรู้นั่นเอง

Helm คืออะไร

สำหรับผม Helm คือเครื่องมือที่ใช้ในการ Deploy application ที่ซับซ้อนบน k8s โดยมี Feature ในการ Install , Update , Rollback , Uninstall ก็ได้ (นิยามจริงๆอ่านได้ที่นี่) ซึ่งจะเห็นว่าพวก Feature ที่ว่าไปนั้นตัว k8s ไม่มีให้ ลองนึกถึงตอนที่ทำการ Uninstall อะไรที่เราพึ่งสั่ง apply ไปสิครับ เราต้องไปไล่หาไฟล์ทั้งหมดที่เราสั่ง apply มาสั่ง delete แต่ถ้าเป็น Helm ล่ะก็คุณสั่งแค่คำสั่งเดียวครับ

Install Helm

ก่อนจะใช้งาน Helm เราต้อง install helm กันก่อนครับ โดยวิธี Install ดูได้ตาม Link นี้เลย : https://helm.sh/docs/intro/install/ ลงแบบไหนเอาตามที่ท่านสะดวกเลย

  • สำหรับใครที่ VM ของผม

ให้แก้ไขไฟล์ : /root/.bashrc

จากนั้นเพิ่มบรรทัดนี้เข้าไปใไฟล์

1
export KUBECONFIG="/etc/rancher/k3s/k3s.yaml"

จากนั้นสั่ง

1
source /root/.bashrc

จากนั้นสั่งกด ctrl + , เพื่อเปิดหน้าจอ Setting และกดตามภาพเราจะเปิดหน้า setting ขึ้นมา

จากนั้นนำข้อมูล JSON ด้านล่างไปใส่ในไฟล์ที่เปิดขึ้นมา

1
2
3
{
"terminal.integrated.defaultProfile.linux": "bash"
}

จากนั้นลองสั่งคำสั่งด้านล่างผ่าน terminal

1
helm list

ถ้าลงสำเร็จจะต้องได้ดังภาพ

เริ่มใช้งาน Helm

ตัวอย่างไฟล์ทั้งหมดสามารถดูได้ที่ Link

ทำการสั่ง command ด้านล่าง

1
helm create whoami

คำสั่งนี้จะเป็นคำสั่งที่สั่งสร้างสิ่งที่เรียกว่า Charts ขึ้นมา โดย Charts ก็เป็นเหมือน Template ที่บอกว่าเราต้องการ Deploy อะไรบ้าง ตัวแปร config ที่จะไปแทนที่ใน Template เป็นอะไร โดยเมื่อสั่งคำสั่งด้านบนเราจะได้ Folder และไฟล์แบบด้านล่าง

โดยสิ่งที่เราจะสนใจคือไฟล์ดังต่อไปนี้

  1. Chart.yaml

    ไฟล์นี้เป็นไฟล์ที่บอกรายละเอียดของ Chart นี้ว่าใช้ version อะไร เอาไปใช้ทำอะไร ให้คนที่เอา Chart เราไปใช้รู้ว่ามันคืออะไร

  2. templates

    Folder นี้เป็น Folder ที่เก็บไฟล์ yml ของ k8s ที่เราต้องการทำเป็น template

  3. values.yaml

    ไฟล์นี้เป็นไฟล์ที่ใส่ค่า config ที่เราจะไปแทนที่ใน template

จากนั้นทำการลบไฟล์ให้หมดจนเหลือแค่นี้ครับ (ลบไฟล์ใน Folder ด้วย)

สร้างไฟล์ template

สร้างไฟล์ whoami.yml ใน folder : templates โดยเราจะสร้าง deployment : whoami จากนั้นให้ยิงเข้ามาผ่าน ingress ที่ domain ชื่อ company-c.com ( อย่าลืม Add Domain name ใน file hosts นะครับ )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami-deployment
labels:
app: whoami
spec:
selector:
matchLabels:
app: whoami-app
template:
metadata:
labels:
app: whoami-app
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
env:
- name: "OWNER"
value: "company-c.com"
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami-ingress
spec:
rules:
- host: "company-c.com"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami-service
port:
number: 80
---
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
selector:
app: whoami-app
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP

จากนั้นสั่งคำสั่งด้านล่างเพื่อให้ helm ทำงาน (เดี๋ยวอธิบายคำสั่งทีหลังนะครับ)

1
helm upgrade --install whoami-helm --namespace whoami-namespace  --create-namespace whoami/

จากนั้นสั่งคำสั่งด้านล่างเพื่อดูว่า helm ทำงานสำเร็จหรือไม่

1
helm list -A

จากนั้นลองสั่งคำสั่งด้านล่างเพื่อเช็คว่ามีอะไรเกิดขึ้นบ้าง

1
2
3
kubectl -n whoami-namespace get deployment
kubectl -n whoami-namespace get service
kubectl -n whoami-namespace get ingress

จากภาพจะเห็นว่ามีการสร้าง deployment , service ,ingress ขึ้นมาให้ตาม template เลย

จากนั้นลองเข้า Browser ไปที่ URL : http://company-c.com?env=true ซึ่งจะพบว่าสามารถเข้าถึง Container whoami ที่เราสร้างขึ้นมาได้

จากทั้งหมดเราจะเห็นว่าคำสั่งของ helm จะทำการสร้าง k8s object ตามไฟล์ที่ใส่ไว้ใน folder : template จากตัวอย่างคือไฟล์ : whoami.yaml

ทำ helm เอาค่า variable มาใส่ใน template ได้

จากตอนที่แล้วเรามีความต้องการที่จะ deploy app หลายๆตัวที่มีโครงสร้าง app เหมือนกันหมดเลย ต่างกันแค่บางค่าที่เฉพาะเช่น เปลี่ยน domain name เปลี่ยนค่า config ให้กับ app เป็นต้น ซึ่งตอนที่แล้วเราใช้ตัว envsubst ในการทำ แต่ตอนนี้เราจะใช้ helm ทำกันครับ

กำหนด Variable ที่จะใช้ในการแทนค่าได้

ส่วนนี้จะคล้ายๆกับการ set environment แต่ของ helm เราต้องกำหนดค่าที่ไฟล์แทน โดยเราจะกำหนดค่าที่ไฟล์ values.yaml โดยตัวไฟล์มีค่าดังนี้ (ค่าเก่าลบทิ้งไปได้เลยครับ)

1
2
domain: "company-c.com"
owner: "NORMAL PROGRAMMER"

ทำให้ Template สามารถเอาค่ามาใส่ได้

เมื่อเรากำหนดค่า variable มาแทนค่าได้แล้ว ส่วนต่อไปที่เราต้องทำคือทำให้ไฟล์ template สามารถเอาค่า variable มาใส่ โดยเราสามารถทำได้โดยการไปแก้ไขไฟล์ : whoami.yml โดยแก้ไขเป็น

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami-deployment
labels:
app: whoami
spec:
selector:
matchLabels:
app: whoami-app
template:
metadata:
labels:
app: whoami-app
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
env:
- name: "OWNER"
value: {{ .Values.owner }}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami-ingress
spec:
rules:
- host: {{ .Values.domain }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami-service
port:
number: 80
---
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
selector:
app: whoami-app
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP

โดยเราจะเห็นว่าส่วนแก้ไขคือ

1
2
{{ .Values.owner }}
{{ .Values.domain }}

ซึ่งมันคือ variable ที่เราประกาศไว้ในไฟล์ values.yaml ที่เราพึ่งกำหนดไป

จากนั้นลองสั่งคำสั่งด้านล่าง

1
helm upgrade --install whoami-helm --namespace whoami-namespace  --create-namespace whoami/

จากนั้นลองเปิด Browser ไปที่ URL : http://company-c.com?env=true ซึ่งจากภาพจะเห็นว่าสามารถเข้าไปที่ company-c.com ตาม value ที่กำหนดใน domain และ environment : onwer นั้นมีค่าเป็น NORMAL PROGRAMMER

คุณสามารถลองเปลี่ยนค่า variable ในไฟล์ values.yaml เป็นค่าอื่นดูแล้วสั่ง helm upgrade ดูว่าค่านั้นเปลี่ยนไปตามที่คุณต้องการหรือไม่

ถ้าอยากลบล่ะ

เราสามารถลบชุดการ deploy ที่สร้างขึ้นมาได้โดยใช้คำสั่ง

1
helm delete -n whoami-namespace whoami-helm

โดยเมื่อเราลองสั่ง list ดูจะเห็นว่าไม่มีข้อมูลชุดการ deploy : whoami-helm เหลืออยู่แล้ว

อธิบาย helm command

helm upgrade

คำสั่งนี้เราได้ใช้มาหลายครั้งแล้ว ส่วนนี้เรามาเข้าใจว่ามันทำอะไร

1
helm upgrade --install whoami-helm --namespace whoami-namespace  --create-namespace whoami/
  • helm upgrade : คำสั่งนี้เป็นการสั่งให้ helm upgrade ชุดการ deploy ที่ระบุ โดยในที่นี้คือสั่งให้ upgrade : whoami-helm

  • –install : ส่วนนี้คือการบอกว่าถ้าไม่เคยสร้างตัวชุดการ deploy มาก่อนให้ทำการสร้าง ในที่นี้คือถ้าไม่เคยมี whoami-helm

  • –namespace : ส่วนนี้คือการบอกว่าตัวชุดการ deploy นั้นจะไปอยู่ใน namespace ไหน ในที่นี้คือไปสร้างใน namespace : whoami-namespace

  • –create-namespace : ส่วนนี้คือการบอกว่าถ้า namespace ที่จะให้ไปอยู่ยังไม่เคยสร้าง ให้ทำการสร้าง namespace ขึ้นมาด้วย

  • whoami/ : ส่วนนี้คือ parameter ที่บอกว่าตัว template และ variable อยู่ที่ใด โดยในที่นี้คืออยู่ใน folder : whoami

รวมแล้วคำสั่งนี้คือการสั่งให้ helm ทำการสร้างชุด deploy ชื่อ whoami-helm ให้อยู่ที่ namespace : whoami โดยรายละเอียดที่ว่าต่างๆจะอยู่ใน folder whoami โดยถ้าชุดการ deploy : whoami-helm ไม่เคยสร้างให้ทำการสร้าง ถ้า namespace : whoami ไม่เคยสร้างให้ทำการสร้าง

helm list

1
helm list -A
  • helm list : คำสั่งนี้เป็นคำสั่งที่ทำการแสดง ชุดการ deploy ทั้งหมดที่สร้างด้วย helm ใน namespace ที่เรากำลังอยู่

  • -A : ส่วนนี้คือบอกว่าให้แสดงทุก namespace

รวมแล้วคำสั่งนี้คือให้แสดงชุดการ deploy ทั้งหมดทีสร้างด้วย helm ในทุก namespace

helm delete

1
helm delete -n whoami-namespace whoami-helm
  • helm delete : คำสั่งนี้เป็นคำสั่งให้ทำการลบชุดการ deploy ที่ต้องการ

  • -n : คือการบอกว่าชุดการ deploy ที่ต้องการลบอยู่ namespace ไหน ในที่นี้คือ namespace : whoami-namespace

  • whoami-helm : คือ parameter ชื่อชุดการ deploy ในที่นี้คือ whoami-helm

รวมแล้วคำสั่งนี้คือการสั่งให้ลบ ชุดการ deploy ที่ชื่อ whoami-helm ที่อยู่ใน namespace : whoami-namespace

อยาก Deploy หลายๆ config

จากที่ส่วนที่ผ่านมาจะเห็นว่าเราสามารถแทน variable ไปใส่ใน template ได้แล้ว และเราสามารถแก้ไข variable ได้ที่ไฟล์ : values.yaml แต่ถ้าลองคิดดีๆมันก็เหมือนว่าเราต้องค่อยๆแก้ไขไฟล์ values.yaml ทุกครั้งที่เราอยากจะ deploy app ด้วย config ที่แตกต่าง ซึ่งนั่นไม่แตกต่างจากการแก้ไขที่ไฟล์ template เลย

แต่ไม่ต้องกังวลครับ helm เขาคิดเรื่องนี้มาแล้วและเปิดช่องทางให้เราสามารถ deploy ด้วยการใช้ values.yaml จากหลายที่ได้

สร้างไฟล์ values ใหม่ขึ้นมาเพื่อใช้ในการ deploy ที่มี config แตกต่าง

ทำการสร้าง folder : config-value จากนั้นทำการสร้างไฟล์

  • values-company-a.yaml
1
2
domain: "company-a.com"
owner: "COMPANY-A-WASINEE"
  • values-company-b.yaml
1
2
domain: "company-b.com"
owner: "COMPANY-B-PICHAYA"

จะเห็นว่าเรากำหนดตัว domain และ owner แตกต่างกันไว้

ทำการ deploy โดยใช้ไฟล์ values.yaml ตามที่เรากำหนด

ลองสั่งคำสั่งด้านล่าง โดยจะเห็นว่ามี parameter เพิ่มเข้ามาคือ -f ซึ่ง parameter ตัวนี้เป็นตัวบอกว่าไฟล์ values.yaml ที่เราอยากใช้นั้นคือไฟล์ไหน

1
2
helm upgrade --install company-a-whoami-helm --namespace whoami-a-namespace  --create-namespace whoami/ -f config-value/values-company-a.yaml
helm upgrade --install company-b-whoami-helm --namespace whoami-b-namespace --create-namespace whoami/ -f config-value/values-company-b.yaml

จากภาพจะเห็นว่าเราได้ชุดการ deploy มาสองตัวคือ company-a-whoami-helm , company-b-whoami-helm ออกมา

มาทดสอบว่าการ deploy นี้ใช้งานได้จริงหรือไม่โดยเข้า browser แล้วไปที่ : http://company-a.com/?env=true

จากภาพจะเห็นว่าสามารถเข้าถึง company-a ได้จริงและค่า environment owner นั้นค่าตรงกับที่ config ไว้ในไฟล์ values-company-a.yaml

มาลองกับ company-b โดยเข้าไปที่ http://company-b.com/?env=true

จากภาพจะเห็นว่าสามารถเข้าถึง company-b และ environment เป็นไปตามที่ config ไว้ใน values-company-b.yaml

สรุป

สำหรับตอนนี้เราได้รู้เกี่ยวกับวิธีการใช้ helm ในการ deploy ประกาศ template ยังไง ประกาศ variable ยังไง และจะเอา variable มาใส่ใน template ยังไง อีกทั้งเรายังสามารถ deploy โดยกำหนดไฟล์ values ตามตำแหน่งที่เราต้องการได้ ซึ่งจากการทำวิธีนี้ทำให้เราสามารถ deploy application ให้แตกต่างตาม config ได้เทียบเท่ากับการใช้ envsubst ในตอนที่แล้ว (อาจจะง่ายกว่าด้วย) สำหรับตอนหน้าเรามาลองใช้ feature อื่นๆของ helm ที่คิดว่าทำให้การ deploy นั้นซับซ้อนขึ้นได้อีก และ คำสั่งที่จะอำนวยความสะดวกเราในการ deploy

Basic Kubernetes Part 7 - ทำโจทย์ - Deploy Wordpress บน k8s

Basic Kubernetes Part 7 - ทำโจทย์ - Deploy Wordpress บน k8s

WordPress เป็น Application หนึ่งที่ถูกเอามาใช้เพื่อสร้างเป็น Web ซึ่งได้รับความนิยมเป็นอย่างมากเพราะคนสามารถเอาไปดัดแปลงทำได้หลายอย่าง ตั้งแต่ Blog บอกเล่าเรื่องราวของคนทั่วไป ไปจนถึง ทำ Web ขายของออนไลน์ก็ได้ ใครอยากรู้ว่า WordPress คืออะไรแบบละเอียด กดที่ Link แล้วหาอ่านเพิ่มเติมได้เลย

วิเคราะห์โจทย์

คราวนี้ถ้าเราจะ Deploy ตัว WordPress ล่ะเราจะต้องทำอะไรบ้าง อย่างแรกคือเราต้องรู้ก่อนว่าตัว WordPress ต้องการอะไรบ้าง และมี Docker ให้ใช้ไหม

มี Docker ให้ใช้ไหมและต้องใช้อะไรบ้าง

เราสามารถ Search ที่ google ด้วยคำว่า WordPress + Docker ซึ่งเราก็เจอที่ Dockerhub โดยเรามี Docker ของ WordPress ให้ใช้ พร้อมบอกวิธีการ Set: ENVIRONMENT ด้วย

ซึ่งจากการดู ENVIRONMENT แล้ว เราจะพบว่าตัว WordPress เนี่ยต้องการใช้ MySQL ด้วย ซึ่งถ้าเรามี MySQL อื่นให้ใช้ก็ OK แต่เราไม่มีดังนั้นแปลว่าเราต้องสร้าง Database ด้วย

สรุปแล้วเราต้อง Deploy container 2 ตัวคือ

  1. WordPress

  2. MySQL

เริ่มสร้างไฟล์เพื่อ Deploy

ตัวอย่างไฟล์ทั้งหมดสามารถดูได้ที่ Link

สร้างส่วนที่เกี่ยวกับ MySQL

สิ่งที่ต้องสร้างสำหรับส่วน MySQL ที่ต้องมีคือ

  1. Deployment ที่มีไว้สร้าง Pod
  2. Service ที่ใช้เป็นตัวเปิดเส้นทางให้สามารถ Request ไปหา Pod ได้
  3. ConfigMap เนื่องจากเราอยากแยก Config ออกจาก Deployment
  4. Persistent Volume เนื่องจากตัว MySQL ต้องมีที่เก็บข้อมูล
  5. Persistent Volume Cliam ต้องสร้างเพื่อให้ Pod สามารถเข้าถึง Volume ที่ต้องการได้

สร้างส่วน PV , PVC

ทำการสร้างไฟล์ mysql-volume.yml จากนั้นสั่ง apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv
spec:
storageClassName: mysql-volume
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
hostPath:
# ตรงนี้เปลี่ยนเป็น path ของเครื่องคุณ
path: /root/volume/mysql

---

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: mysql-volume
resources:
requests:
storage: 2Gi

โดยไฟล์นี้จะทำการสร้าง pv กับ pvc เพื่อใช้เก็บข้อมูลของ MySQL โดยในการใช้งานจริงเราไม่ควรใช้ Storage ประเภท hostPath นะครับ เพราะมันจะทำให้เวลา Deploy เราจะต้องบังคับให้ Pod อยู่กับ Node ที่มี Volume อยู่เท่านั้น ไม่สามารถกระจายไป Node ต่างๆได้ ในงานจริงควรจะเป็นพวก nfs ครับ

สร้าง ConfigMap

สร้างไฟล์ mysql-configmap.yml จากนั้นสั่ง apply

1
2
3
4
5
6
7
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-configmap
data:
MYSQL_ROOT_PASSWORD: "root"
MYSQL_DATABASE: "wordpress_db"

เนื่องจากเราต้องการแยก config ออกจาก deployment เราจึงแยกออกมาเป็น ConfigMap โดยค่าที่อยู่ใน configMap คือ ENVIRONMENT ที่เราอยากจะเอาไป set ใน pod โดย Environment ที่เราจะ set คือ MYSQL_ROOT_PASSWORD (อันนี้คือตั้งค่า root password ) , MYSQL_DATABASE (อันนี้คือสร้าง database ชื่ออะไร)

สร้าง Deployment

สร้างไฟล์ mysql-deployment.yml จากนั้นสั่ง apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-pod
labels:
app: mysql-app
spec:
selector:
matchLabels:
app: mysql-app
template:
metadata:
labels:
app: mysql-app
spec:
containers:
- name: mysql-app
imagePullPolicy: Always
image: mysql:5.7
envFrom:
- configMapRef:
name: mysql-configmap
ports:
- containerPort: 3306
name: mysql-port
volumeMounts:
- mountPath: /var/lib/mysql
name: mysql-volume
volumes:
- name: mysql-volume
persistentVolumeClaim:
claimName: mysql-pvc

ไฟล์นี้ทำการสร้าง Deployment โดยใช้ environment จาก oonfigMap : mysql-configmap และทำการ mount volume ไปที่ /var/lib/mysql โดยใช้ volume จาก mysql-pvc

สร้าง Service

ทำการสร้างไฟล์ : mysql-service.yml จากนั้นสั่ง apply

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
selector:
app: mysql-app
ports:
- protocol: TCP
port: 3306
targetPort: 3306
nodePort: 32020
type: NodePort

โดยไฟล์นี้ทำการสร้าง Service type NodePort โดยให้เข้าที่ port : 32020 จากนั้นลองตรวจสอบว่าสามารถใช้งาน

จากภาพผมใช้ตัว dbeaver เข้าไปตรวจสอบว่าสามารถใช้งาน MySQL ได้ไหมและมี Database ชื่อ wordpress_db ที่สั่งสร้างไหม ซึ่งปรากฏว่าดังที่สร้าง

สร้างส่วนที่เกี่ยวกับ WordPress

ในส่วนของ WordPress นั้นมีส่วนที่เราต้องสร้างดังต่อไปนี้

  1. ส่วน Deployment เพื่อทำการสร้าง Pod
  2. Service ที่ใช้เป็นตัวเปิดเส้นทางให้สามารถ Request ไปหา Pod ได้
  3. ConfigMap เนื่องจากเราอยากแยก Config ออกจาก Deployment
  4. Ingress เนื่องจากเราต้องการใช้งาน WordPress ผ่าน Port 80

ในส่วนของ Volume นั้นเราไม่ได้ใช้เพราะเราจะทำการ Save ข้อมูลลง MySQL ซึ่งเราสร้างไปแล้ว

สร้าง ConfigMap

สร้างไฟล์ wordpress-configmap.yml จากนั้นสั่ง apply

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: ConfigMap
metadata:
name: wordpress-configmap
data:
WORDPRESS_DB_HOST: "mysql-service.default.svc.cluster.local"
WORDPRESS_DB_USER: "root"
WORDPRESS_DB_PASSWORD: "root"
WORDPRESS_DB_NAME: "wordpress_db"
WORDPRESS_TABLE_PREFIX: "wasinee_"

จากไฟล์เราสร้าง configMap โดยค่า configMap นี้จะเอาไปใช้เป็น Environment โดย Environment ที่เราตั้งค่าไว้จะเกี่ยวกับ Database เพราะ WordPress ต้องใช้ database หากสังเกตจะเห็นว่า WORDPRESS_DB_HOST นั้นเราชี้ไปหา service : mysql-service ที่เราสร้างโดยใช้ชื่อ service (ใครงงกลับไปอ่านตอน service นะครับ)

สร้าง Deployment

สร้างไฟล์ wordpress-deployment.yml จากนั้นสั่ง apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress-deployment
labels:
app: wordpress-app
spec:
selector:
matchLabels:
app: wordpress-app
template:
metadata:
labels:
app: wordpress-app
spec:
containers:
- name: wordpress-app
imagePullPolicy: Always
image: wordpress:6.3.1-apache
envFrom:
- configMapRef:
name: wordpress-configmap
ports:
- containerPort: 80
name: wordpress-port

โดยจากไฟล์จะเห็นว่าเราสร้าง wordpress โดยเอา environment จาก configMap : wordpress-configmap และเปิด port 80

สร้าง Service

สร้างไฟล์ wordpress-service.yml จากนั้นสั่ง apply

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: wordpress-service
spec:
selector:
app: wordpress-app
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 32030
type: NodePort

จากไฟล์เราจะทำการสร้าง service แบบ NodePort ที่ port 32030 แล้วชี้ไปที่ pod ที่มี label เป็น app = wordpress-app

ทำการทดลองเปิด Browser แล้วเข้า URL - http://<ip ของเครื่องที่ run>:32030 ของผมเป็น http://192.168.156.101:32030 ซึ่งเมื่อเปิดแล้วจะได้เป็นดังภาพ

จะเห็นว่าเราสามารถเปิด Wordpress ได้ (ไม่ต้องตกใจครับ มันคือหน้า install เนื่องจากเราเข้ามาครั้งแรก)

ส่วน Ingress

สร้างไฟล์ wordpress-ingress.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wordpress-ingress
spec:
rules:
- host: "wordpress-application.com"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: wordpress-service
port:
number: 80

สุดท้ายเป็นการสร้าง ingress ให้สามารถเข้าผ่าน port 80 ได้ โดยเราตั้งกฏว่าถ้าเข้ามาด้วย domain name : wordpress-application.com จะ route ไปที่ service : wordpress-service

เรามาทดสอบว่า ingress ว่าใช้งานได้ไหมโดยเปิด Browser ไปที่ : http://wordpress-application.com (อย่าลืม set file hosts ก่อนนะครับ)

ซึ่งจากภาพจะเห็นว่าเราสามารถเปิด wordpress ได้

ลองใช้งาน Wordpress เพื่อตรวจสอบ

ลองมาใช้งาน wordpress เพื่อทดสอบว่ามีปัญหาอะไรไหม โดยเราลองทำการ install และใช้งาน

จากนั้นลอง login ด้วย username , password ที่เราใส่ไปตอน install

ซึ่งเมื่อ Login เข้าไปแล้วจะเข้าไปหน้า Manage ดังภาพ

ซึ่งจากการทดลองเล่นนั้นไม่พบว่าติดปัญหาอะไร ซึ่งนั่นก็พอจะการันตีได้แล้วว่าเรา Deploy ตัว WordPress สำเร็จ

แล้วถ้าอยาก Deploy wordpress แบบนี้อีกหลายตัวล่ะ

ถ้าสมมติมีความต้องการ Deploy : wordpress อีกหลายๆตัวล่ะ เช่น บริษัท A , บริษัท B , บริษัท C wordpress ของละบริษัทต้องมี mysql ของตัวเอง domain name ของตัวเอง แล้วเราทำอะไรได้บ้าง

1. Copy File

ง่ายๆเลยก็คือเรา copy ไฟล์ที่เราสร้างทั้งหมดแล้วไปแก้ไฟล์ตามแต่ละบริษัทต้องการ วิธีนี้ก็ง่ายครับเราสามารถทำได้เลย แต่ปัญหาถ้าบริษัทที่ต้องการไม่ใช่ 3 บริษัทแต่เป็น 100 บริษัทล่ะจะเป็นยังไง แค่นึกภาพว่าต้อง copy ไฟล์ทุกไฟล์ที่ว่ามาแล้วไปไล่แก้แต่ละไฟล์ก็เหนื่อยแล้ว

2. ใช้ envsubst

เราจะใช้ envsubst ร่วมกับการใช้ environment เพื่อทำการ Deploy หลายๆ config

โดยเราจะใช้ envsubst กับค่าที่เปลี่ยนแปลงไปในแต่ละบริษัทตัวอย่างเช่น ตัวอย่างเช่น domain name โดยเราจะทำประมาณนี้

ทำการสร้างไฟล์ : wordpress-ingress-envsubst.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wordpress-ingress
spec:
rules:
- host: "$DOMAIN_NAME"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: wordpress-service
port:
number: 80

จากนั้นลองสั่ง

1
2
export DOMAIN_NAME=wasinee-application.com
envsubst < wordpress-ingress-envsubst.yaml

จากภาพจะเห็นว่าค่าของ DOMAIN_NAME ที่เรา set ไว้นั้นไปแทนที่ค่าที่ $DOMAIN_NAME ในไฟล์ wordpress-ingress-envsubst.yaml โดยหากเราจะใช้วิธีนี้เราต้องทำดังต่อไปนี้

  1. สร้าง template file
  2. สร้าง environment file
  3. สร้าง script เพื่อทำการ deploy
สร้าง Template file

ทำการสร้างไฟล์ whoami-template.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
apiVersion: v1
kind: Pod
metadata:
name: whoami-pod
labels:
app: whoami
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
env:
- name: "OWNER"
value: "$OWNER"
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami-ingress
spec:
rules:
- host: "$DOMAIN_NAME"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami-service
port:
number: 80
---
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
selector:
app: whoami
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP

จากไฟล์จะเห็นว่ามีการใช้ environment 2 ในไฟล์คือ

  • OWNER : อันนี้มีไว้ set ใน whoami เพื่อใช้ดูว่าค่าถูก set จริงไหม และไปถูก Pod ไหม
  • DOMAIN_NAME : อันนี้มีไว้ set domain name ไว้ใช้ตอน ingress
สร้าง environment file

สร้างไฟล์ company-a.sh และใส่ค่าตามด้านล่าง

1
2
export DOMAIN_NAME=company-a.com
export OWNER=company-a

สร้างไฟล์ company-b.sh และใส่ค่าตามด้านล่าง

1
2
export DOMAIN_NAME=company-b.com
export OWNER=company-b

ส่วนนี้คือการกำหนด environment ตามที่เราจะไปใช้กับ Template และ script โดยเราจะทำ 2 ไฟล์คือ company-a.sh กับ company-b.sh ซึ่งถ้าโดยไม่ต้องสน file extension มันคือไฟล์ config ดีๆนี่เอง

สร้าง script เพื่อทำการ deploy

สร้างไฟล์ deploy.sh

โดย script นี้ไม่มีอะไรมากคือทำการรับ parameter 1 ตัวเข้ามา ซึ่ง parameter ที่รับเข้ามาคือไฟล์ environment จากนั้นก็จะไปทำการ set environment ด้วยคำสั่ง source จากนั้นจะทำการเช็ค namespace ว่าเคยมีไหม ถ้าไม่มีจะทำการสร้าง (namespace ขอติดไว้ก่อน แต่มองว่า namespace เหมือนเป็นจักรวาลใน marvel ละกัน ถ้าอยู่คนละ namespace ถือว่าอยู่คนละจักรวาล ยุ่งเกี่ยวกันยากถ้าไม่ผ่านช่องทางที่กำหนด) จากนั้นทำการใช้คำสั่ง envsubst เพื่อแทนค่า environment ไปใน template และส่งผลลัพธ์นั้นไปให้คำสั่ง kubectl apply ทำงานต่อ

1
2
3
4
5
6
7
8
9
10
11
12
echo $1

# Set environment
source $1

# ทำการสร้าง namespace ในกรณีที่ไม่มี namespace
if ! kubectl get ns $OWNER > /dev/null 2>&1; then
kubectl create namespace $OWNER
fi

# ทำการเรียกใช้ envsubst เพื่อแทนค่า environment ลงไปในไฟล์ template จากนั้นส่งค่าไฟล์ที่ถูกแทนค่าแล้วไปให้คำสั่ง kubectl apply
envsubst < whoami-template.yml | kubectl apply --namespace=$OWNER -f -
ลอง Deploy

ตัวอย่างไฟล์ทั้งหมดเกี่ยวกับการทำ template deploy สามารถดูได้ที่ : Link

เดี๋ยวผมจะทำตัวอย่างการ Deploy โดยใช้คำสั่งด้านล่าง

1
2
3
4
5
# deploy โดยใช้ environment config ของ company-a
bash deploy.sh company-a.sh

# deploy โดยใช้ environment config ของ company-b
bash deploy.sh company-b.sh

จากนั้นลองสั่งด้านล่างเพื่อเช็คว่าสามารถสร้าง pod และ service สำเร็จหรือไม่

1
2
kubectl get all --namespace company-a
kubectl get all --namespace company-b

ซึ่งจะเห็นว่ามีการสร้าง pod และ service ขึ้นมาทั้ง 2 namespace(จักรวาล) และสังเกตุจะเห็นว่าชื่อ pod และ service เหมือนกัน ซึ่งในกรณีที่เราทำแบบไม่กำหนด namespace มันจะไปอยู่ที่ default ซึ่งถ้ามีชื่อซ้ำกันมันจะทำการ update ดังนั้นถ้าอยากให้ชื่อซ้ำกันได้ก็ต้องแยกไปอยู่คนละ namespace (ลองเทียบในหนัง จักรวาล 616 มี Spider man ได้คนเดียว แต่จักรวาลอื่นก็มี Spider man ได้เหมือนกัน )

คราวนี้เรามาลองเปิด Browser แล้วเข้า URL ดังต่อไปนี้เพื่อเช็คว่า Ingress ทำงานได้ไหมและ Route ไปถูกเครื่องรึเปล่า (อย่าลืม set file hosts)

1
2
http://company-a.com/?env=true
http://company-b.com/?env=true

จากภาพจะเห็นว่า Ingress route ได้ถูกดูได้จากค่า OWNER ที่วงแดงไว้ ซึ่งตรงกับการ config ใน environment ของเรา

วิธีนี้ดียังไง

วิธีนี้ดีกว่าวิธีแก้ไฟล์ยังไง อย่างแรกคือคุณไม่ต้องตามไปแก้ไฟล์ yml ทุกไฟล์ คุณต้องทำการแก้แค่ไฟล์ environment config เพียงไฟล์เดียว ถ้าในตัวอย่างที่เราทำหากต้องเพิ่ม company-c เราก็แค่สร้างไฟล์ company-c.sh จากนั้นกำหนดค่าให้ไฟล์ environment config เท่านั้น แถมเวลาอัพเดทเกี่ยวกับการ Pod service ingress ต่างๆนาๆ เราสามารถแก้ที่ชุด template ที่เดียวเลย ไม่ต้องไปไล่แก้ทุกอันที่เคย Deploy (ถ้า Deploy ไปแล้ว 100 เจ้า แล้วจะเพิ่มอะไรสักอย่างแล้วต้องไปแก้ 100 เจ้า สยองขวัญน่าดู)

มีวิธีที่ดีกว่านี้ไหม

คำตอบคือมีครับ หากเราลองคิดดีๆการที่เราทำมันคือการทำงานเกี่ยวกับ Template processor แบบพวก PHP ที่สามารถใส่ตัวแปรไปใน HTML แล้วสั่ง rendering หรือ JSP และ Template engine อีกมากมาย โดยพวก Template engine พวกนี้จะมีลูกเล่นอีกมากมายที่สามารถทำได้ เช่น if else กับ template เช่น ถ้าค่า type เป็น VIP ให้ทำการใช้ template ส่วนนี้ด้วย หากไม่ใช่ให้ใช้ template ที่ไม่มีส่วนนี้ ซึ่งในงานจริงคุณต้องเจอมันแน่ๆ เช่น บริษัท W เป็นลูกค้าระดับ VIP ดังนั้นเขาต้องการการทำงานที่เร็วกว่าปกติ เราจึงต้องเอา REDIS มาทำ CACHE ในการดึงข้อมูล ดังนั้นจะต้องมี Pod ของ REDIS เพิ่มเข้ามา แต่เพิ่มเข้ามาแค่เฉพาะลูกค้าที่เป็น VIP ถ้าที่เราทำอยู่ตอนนี้น่าจะไม่พอในการทำแบบนั้น

แต่โชคดีครับที่ชาวโลกมีคนเห็นปัญหานี้เหมือนกันและได้สร้างสิ่งที่มาช่วยในการทำแบบนี้ซึ่งนั่นก็คือ HELM ซึ่ง HELM เนี่ยทำได้มากกว่าเป็น Template engine มันสามารถทำ version ของการ Deploy ได้ด้วย ประมาณว่าถ้าคุณทำตัว template deploy ใหม่ไปอัพเดทแล้วเกิดปัญหา คุณสามารถสั่งให้ helm เนี่ยกลับไปใช้ตัว deploy ก่อนหน้าที่ไม่มีปัญหาได้เลย

สรุป

สำหรับตอนนี้เราได้ลองทำโจทย์ Deploy Wordpress กันไปแล้ว ซึ่งจะเห็นว่าไม่ได้ยากมากเลย แถมเรายังได้รู้ปัญหาเกี่ยวกับ Deploy และทำให้เรามารู้จักกับ Tool ที่ใช้ตัว Deploy ที่เราจะไปเรียนรู้กันในตอนต่อไปซึ่งนั่นก็คือ HELM

Basic Kubernetes Part 6 - Ingress

Basic Kubernetes Part 6 - Ingress

ในการติดต่อกันระหว่าง Client Server ในชั้น Application Layer นั้นเราจะใช้ IP กับ Port ซึ่ง Protocol HTTP ใช้ port 80 , HTTPS ใช้ port 443 ทุกอย่างเหมือนไม่มีปัญหาอะไร แต่ปัญหาเกิดขึ้นเมื่อเครื่องเรา deploy application หลายๆ appliaction ซึ่งหลายๆ application นั้นใช้ protocol http , https ซึ่งเราไม่สามารถให้ 2 pod ใช้ Port เดียวกันได้ จะให้ใช้ Port แบบ 32010 , 32020 ก็ไม่สะดวกเพราะ คนใช้งานต้องกำหนด Port มาใน URL ซึ่งน่าจะสร้างความลำบากให้ผู้ใช้น่าดู

รูปภาพ App หลายๆตัวอยู่บนเครื่องเดียวกัน

ถ้าเป็นการ Deploy แบบไม่ใช่ k8s นั้นเรามีวิธีทำคือ เราก็สร้าง Reverse proxy (nginx , apache) ขึ้นมาแล้ว Run บน Port 80 , 443 จากนั้นก็ให้มันรับ Request แล้ว routing ตามกฏไปหา Application ที่ต้องการ

รูปภาพ App ที่มี nginx อยู่ที่ port 80, 443

คราวนี้ถ้าเราอยากทำแบบนี้บน k8s ล่ะจะทำยังไง จากความรู้ที่เรามีเราไม่สามารถทำได้ เพราะตอนเราสร้าง service แบบ NodePort ไม่สามารถตั้งค่า Port เป็น 80 , 443 ได้ ดังนั้นเราไม่สามารถเอา nginx หรือ apache ไปวางได้ แล้วเราจะทำยังไงล่ะ

จากความต้องการแบบนี้ตัว k8s ได้กำหนดสิ่งที่เรียกว่า Ingress ขึ้นมามาให้เราเพื่อทำตามความต้องการที่อยากได้ โดย Ingress นี้เป็นแค่ Spec ของ Configfile เท่านั้นไม่มีตัวที่ทำงานจริง ตัวที่ทำงานจริงคือ Ingress controller ซึ่ง Ingress controller มีให้เราใช้มากมายตัวอย่างเช่น

โดยใครอยากใช้ Ingress controller ตัวไหนก็ลองเข้าไปดูวิธี Install ที่ Document ของแต่ละเจ้าได้เลย ส่วนใครที่ใช้ VM ของผมนั้น ตัว k3s เขาลงตัว Traefik Ingress controller มาให้แล้ว ดังนั้นไม่ต้องลงเพิ่มนะครับ

ลองสร้าง Ingress กัน

ตัวอย่างไฟล์ Config สามารถได้ดูได้ที่ Link

เตรียม Application (Pod)

อันดับเแรกมาทำการสร้าง Application (Pod) บน k8s กันก่อน โดยสร้างไฟล์ : nginx-whoami-app.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami-deployment
spec:
selector:
matchLabels:
app: whoami
template:
metadata:
name: whoami-pod
labels:
app: whoami
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 32010
type: NodePort
---
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
selector:
app: whoami
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 32020
type: NodePort

จากนั้นทดสอบเปิด Browser แล้วเข้าไปที่ URL

จากภาพจะเห็นว่าเราสามารถเข้าถึง nginx ผ่าน port 32010 และ whoami ผ่าน port 32020

Set file host

เนื่องจากเราไม่ได้จด Domain name : nginx-application.com และ whoami-application.com ไว้ (ต่อให้จดได้ก็จะไม่ได้ ip ตรงกันเพราะแต่ละเครื่องที่ run k8s มี ip ไม่เหมือนกัน) ดังนั้นจะไม่สามารถ resolve domain name ได้ แต่เราสามารถแก้ปัญหานี้ได้โดยการกำหนดค่าในไฟล์ : hosts ซึ่งไฟล์ host นี้แต่ละ os อยู่ไม่เหมือนกัน โดยแต่ละ os ไฟล์จะอยู่ที่

  • windows : c:\Windows\System32\Drivers\etc\hosts
  • linux : /etc/hosts
  • macOs : /private/etc/hosts

เมื่อเปิดไฟล์ให้และเพิ่มข้อมูลลงไปคือ

1
2
<ip เครื่องที่ run k8s> nginx-application.com
<ip เครื่องที่ run k8s> whoami-application.com

ซึ่งของผมจะได้เป็น

1
2
192.168.156.101 nginx-application.com
192.168.156.101 whoami-application.com

ทำการทดสอบลองใช้คำสั่ง ping ไปที่ nginx-application.com , whoami-application.com ว่าทำการ resolve ip เป็น ip ที่ตั้งไว้หรือไม่

1
2
ping nginx-application.com
ping whoami-application.com

สร้าง Ingress

สร้างไฟล์ basic-ingress.yml จากนั้นสั่ง apply file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: basic-ingress
spec:
rules:
- host: "nginx-application.com"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
- host: "whoami-application.com"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami-service
port:
number: 80

จากนั้นลอง get และ describe ingress ดูครับ

จากภาพจะเห็นว่ามี Ingress ชื่อ basic-ingress ขึ้นมาและมีการบอก host และ path ซึ่งตรงกับที่เรากำหนดในไฟล์ อีกทั้งมีชื่อ service คือ nginx-service , whoami-service

จากนั้นลองเข้า browser แล้วเปิด URL

  • nginx-application.com
  • whoami-application.com


จากภาพจะเห็นว่าสามารถเปิดหน้าได้ตรงตามความต้องการของเราคือ เข้า nginx-application.com ก็ไปหา nginx เข้า whoami-application.com ก็ไปหา whoami ซึ่งนั่นแปลว่า Ingress ของเราทำงานได้ตรงตามความต้องการของเราแล้ว

อธิบายไฟล์ basic-ingress.yml

ในส่วนของ apiVersion , kind , metadata ไม่ขอลงรายละเอียดเพราะเหมือนกับตัวอื่นๆที่ผ่านมา

rules

ตรงนี้จะเป็นการประกาศเกี่ยวกับ rule (มีได้หลาย rule) ที่ใช้ในการ route ของ Ingress (Reverse Proxy) ถ้าเทียบง่ายๆก็เหมือน file nginx.conf ของ nginx ที่เรากำหนดว่ามาด้วย host นี้จะให้ส่งไปที่ไหนต่อ โดย rule จะเป็นไปตามรายละเอียดต่อไปนี้

1
2
3
4
5
6
7
8
9
10
- host: "nginx-application.com" 
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80

host

ตรงส่วนนี้จะเป็นการบอกกฏว่า request ที่มาต้องมีค่า host ตรงกับ nginx-application.com

http

ตรงนี้เป็นการบอกว่าถ้าเป็น protocol http

paths

ตรงนี้จะเป็นการบอกต่อว่าเมื่อเข้ามาด้วย http แล้วจะใช้ path อะไรในการ map (มีได้หลาย path)

path

1
2
3
4
5
6
7
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80

ตรงนี้จะเป็นการบอกรายละเอียดแล้วว่าต้องตรงด้วย path อะไร ลักษณะของการ map เป็นแบบไหน ถ้า map ตรงแล้วจะให้ยิงไปที่ service ไหน port อะไร

จากตัวอย่างจะเป็นการว่า ถ้า map ที่ path : / การ map เป็นแบบ Prefix คือถ้าขึ้นต้นด้วย / จะถือว่าตรงตามเงื่อนไขหมด ซึ่งถ้าตรงตามเงื่อนไขทั้งหมดเนี่ยให้ route request นี้ไปหา service : nginx-service ที่ port 80

สรุปการ config rule

1
2
3
4
5
6
7
8
9
10
- host: "nginx-application.com" 
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80

rule นี้มีความหมายคือ request ที่ยิงเข้ามามีค่า host == nginx-application.com แล้วมี path ที่ขึ้นต้นด้วย / จะถูก route request ไปที่ nginx-service port 80

สิ่งที่ต้องอ่านเพิ่มเติม

การตั้งค่าเพิ่มเติม Ingress Controller

ที่อยู่ในตัวอย่างทั้งหมดของเรานั้นเป็น Ingress กลางๆที่ k8s กำหนดขึ้นให้ใช้ได้กับทุก IngressController ซึ่ง Rule ที่ตั้งได้นั้นมีจำกัดมากๆแค่ host กับ path จะ route ด้วยเงื่อนไขอื่นๆ เช่น header ที่เรากำหนดเป็นต้น หรือ เวลา Route แล้วให้เราทำการแก้ไข path เพิ่ม header ที่ต้องการ อันนี้ไม่สามารถทำได้ผ่านการประกาศแบบนี้

แต่ถามว่าทำได้ไหม ก็ต้องบอกว่าสามารถทำได้ครับ แต่ต้องเพิ่มการประกาศตาม Ingress controller ที่คุณเลือกใช้ เช่น

  • Nginx : เราสามารถใช้ Feature ต่างๆแบบที่ nginx reverse proxy ทำได้แต่ต้องประกาศในส่วนของ Annotation โดยสามารถดูรายละเอียดการ config ได้ที่ Link
  • Traefik : ถ้าอยากใช้ Feature ที่มากกว่าที่ k8s กำหนดต้องประกาศตัว IngressRoute ของ Traefik เพิ่ม โดยสามารถดูรายละเอียดการ config ได้ที่ Link และดู Annotation ได้ที่ Link
  • Contour : ตัวนี้จะคล้ายๆกับตัว Traefik ที่ต้องทำการประกาศ Ingress ของตัว Contour เองเพื่อใช้ Feature ของ Contour โดยสามารถดูรายละเอียดการ Config ได้ที่ Link

การ Set ssl ให้กับ Ingress

ในตัวอย่างที่เราได้ทดลองนั้น เราทดลองสร้าง Ingress ที่เป็นแบบ http แต่ในการทำงานจริงๆนั้นคนที่ใช้งานจริงๆนั้นใช้งานแบบ https นั้นจะต้อง set อย่างไร โดยสามารถดูรายละเอียดการ setting ได้ที่ : Link

สรุป

สำหรับตอนนี้เราได้ทำความรู้จักกับ Ingress ว่าคืออะไร เอาไปใช้อะไรกับกรณีไหน rule ที่เราพอตั้งให้ได้คืออะไร ซึ่งถ้าใครอ่านมาตั้งแต่ตอนแรกจนถึงตอนนี้ก็น่าจะสามารถ Deploy application ขึ้น k8s ไปใช้งานได้แล้ว (แต่ไม่ได้ดีมาก และไม่ถูกต้องตาม best practice ) แต่อ่านอย่างเดียวอาจจะไม่เข้าใจ ตอนหน้าเรามาลองทำการ Deploy application สักตัวกันว่าถ้าต้องทำโจทย์แบบนี้แล้วเราต้องทำอะไรบ้าง

Basic Kubernetes Part 5 - ConfigMap และ Secret

Basic Kubernetes Part 5 - ConfigMap และ Secret

ตอนนี้เรามาทำความทำความรู้จัก ConfigMap และ Secret กันว่าคืออะไร

รูปแบบการ Config ที่เป็นที่นิยม

สิ่งหนึ่งที่ขาดไม่ได้ของ Application คือการ Configuration เช่น nginx ต้องทำการ Config เปลี่ยน Endpoint ของ Reverse proxy หรือตัว Application เองต้องการกำหนดตัว Connection Pool ของ Database ให้มากขึ้นหรือลดลง หรือ แก้ไข Maximum file size ที่จะ Upload เข้ามาใน Application การ Config

Application นั้นนิยมทำการ Config ผ่านไฟล์ แต่ในปัจจุบันที่ใช้ Container กันนั้นเปลี่ยนมา Config ผ่านตัว Environment แทน เนื่องจากเราไม่อยากเข้าไปยุ่งกับตัว Volume เพราะเมื่อไหร่ที่ยุ่งกับ Volume จะมีข้อจำกัดต่างๆ เช่น Volume นี้ต้อง Mount จากไหน อ่านได้จากกี่ Node แต่ถ้าเป็น Environment นั้น ตัวสร้างสามารถกำหนดค่า Environment ผูกไปกับ Container ตอนสร้างได้เลย ซึ่งจะไป Run ที่ Node ก็ได้ ดังนั้นการ Config ผ่าน Environment จึงเป็นที่นิยม

เพื่อให้ทำการ Config ทั้ง 2 แบบได้ ทาง k8s ก็ได้ทำการสร้างสิ่งอำนวยความสะดวกในการทำการ Config ขึ้นมาซึ่งนั่นก็คือ ConfigMap โดย ConfigMap นั้นสามารถ Mount volume ไปเป็น File ได้และความพิเศษของมันก็คือมันสามารถถูกเข้าถึงได้จากทุก Node ใน Cluster ของ k8s ดังนั้น ConfigMap จึงแก้ปัญหาการ Config แบบ File ได้

ConfigMap คืออะไร

ConfigMap คือ Object แบบ KeyValue ที่ตัว Pods สามารถเข้าถึงและเอาไปเป็น Environment เอาไป Mount Volume เป็นไฟล์ (นิยามเต็มไปอ่านที่ : Link )

ตัวอย่างไฟล์ Configuration ทั้งหมดในตอนนี้อยู่ที่ Link

สร้าง ConfigMap กัน

สร้างไฟล์ basic-configmap.yml

1
2
3
4
5
6
7
8
apiVersion: v1
kind: ConfigMap
metadata:
name: basic-configmap
data:
config1: "data config 1"
config2: "data config 2"
CONFIG_DATABASE: "EXAMPLE CONFIG"

จากนั้นสั่ง apply และลอง get และ describe ตัว configMap ดู

จะเห็นว่ามี ConfigMap ที่เราสร้างโผล่ขึ้นมาและเมื่อ Describe ดูค่าแล้วเราจะเห็นค่าที่เรากำหนดไว้กับ key ต่างๆเลย เช่น key : config1 มี value : data config 1

อธิบายไฟล์ basic-configmap.yml

ส่วนของ apiVersion , kind , metadata ไม่ขออธิบายนะครับเพราะเหมือนกับ Pod , Deployment , Service

data

ตรงนี้จะเป็นการประกาศ Key , Value ของ ConfigMap โดยตัวอย่างจะเป็น

1
2
3
config1: "data config 1"
config2: "data config 2"
CONFIG_DATABASE: "EXAMPLE CONFIG"

ซึ่งหมายความว่า key : “config1” มี value : “data config 1” key : config2 มี value : “data config 2” โดยเราสามารถประกาศตัว key value ได้ตามใจเพียงค่าอย่าให้ key ซ้ำกันก็พอ

เอา ConfigMap ไปเป็น Environment

จากตัวอย่างที่เราทำมาเราจะทำการ set environment ของ Pod ผ่านตรง env เช่นตัวอย่างด้านล่าง

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
kind: Pod
metadata:
name: whoami-pod
labels:
app: whoami
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
env:
- name: config1
value: "data config 1"
- name: config2
value: "data config 2"
- name: CONFIG_DATABASE
value: "EXAMPLE CONFIG"

ซึ่งถ้า Config env มันเยอะตรงนี้มันก็จะดูมากมายเต็มไปหมด เวลา Config อะไรก็ต้องมาแก้การ Define Pod ตลอด ดังนั้นถ้าเราแยกส่วน Config ออกมาเวลาแก้ไขก็แก้ที่ Config เลยน่าจะดีกว่า (ทีม Config จะได้ยุ่งแค่ Config ไฟล์เดียวไม่ต้องมายุ่งกับการประกาศ Pod ) ซี่งเราสามารถทำได้โดยประกาศแบบนี้

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# whoami-pod-002.yml
apiVersion: v1
kind: Pod
metadata:
name: whoami-pod
labels:
app: whoami
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
envFrom:
- configMapRef:
name: basic-configmap

เพื่อให้ง่ายต่อการเช็ค Environment ผมขอใช้ตัว image : whoami เพราะมันมี api ให้เรายิงไปดู Environment ที่ถูก set ไว้ให้กับ container ได้ (สามารถดูรายละเอียดได้ที่ : Link ) ดังนั้นผมขอสร้าง Service เพื่อเรียกผ่าน Browser นะครับ จะได้ไม่ต้องมานั่ง Describe ตัว Pod หา IP แล้วยิง

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# whoami-service.yml
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
selector:
app: whoami
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 32020
type: NodePort

ให้เราเปิด Browser แล้วเปิด URL : http://<ip ของเครื่องที่ run k8s>:32020/api?env=true (ของผมเป็น http://192.168.156.101:32020/api?env=true)

จากภาพจะเห็นว่าเมื่อยิงไปจะได้ json response กลับมาจะมีส่วนที่เป็น environment กลับมาดังภาพ ซึ่งนั่นแปลว่าตัว Pod whoami ถูก Set environment จาก ConfigMap จริงๆ

อธิบายไฟล์ whoami-pod-002.yml

ในไฟล์นี้จะมีส่วนที่เพิ่มเติมมาคือ envFrom

envFrom

ตรงส่วนนี้จะเป็นการบอกว่าจะเอา Environment มาจากไหน

1
2
3
envFrom:
- configMapRef:
name: basic-configmap

โดยจากตัวอย่างเนี่ยเราบอกว่าให้เอามาจาก configMap โดยบอกจาก keyword : configMapRef โดยเอามาจาก ConfigMap ชื่อ basic-configmap ซึ่งการประกาศแบบนี้คือเอาทุกค่าใน ConfigMap มาใส่ในนี้เลย

อยาก Set แค่บาง Environment ล่ะ

ในส่วนที่แล้วเราลอง Set environment ผ่าน ConfigMap ซึ่งเป็นการเอามาใส่ทั้ง ConfigMap แต่บางกรณีเราไม่อยากเอาค่าทุกค่าจาก ConfigMap ไปเป็น environment เราอยากได้แค่บางค่าเราจะทำยังไง แน่นอนว่านี่คือปัญหาทั่วไปที่ทุกคนอยากได้ซึ่ง k8s ก็ทำให้เราสามารถทำแบบนั้นได้ครับ โดยทำแบบนี้

ทำการสร้างไฟล์ : whoami-pod-003.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# whoami-pod-003.yml
apiVersion: v1
kind: Pod
metadata:
name: whoami-pod
labels:
app: whoami
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
env:
- name: "OWNER"
value: "WASINEE"
- name: "CONFIG_DATABASE_FOR_CONNECTION"
valueFrom:
configMapKeyRef:
name: "basic-configmap"
key: "CONFIG_DATABASE"

จากนั้นสั่งคำสั่งด้านล่างและเปิด Browser และใส่ url เดิมลงไป

1
2
3
# สั่งเพื่อลบ pod ตัวเก่าทิ้งไป
kubectl delete whoami-pod
kubectl apply -f whoami-pod-003.yml

จากภาพจะเห็นว่า Environment ถูก Set แค่ค่าที่ต้องการจริงๆคือ OWNER (อันนี้ประกาศแบบธรรมดา) กับ CONFIG_DATABASE_FOR_CONNECTION (อันนี้ประกาศแบบใหม่)

อธิบายไฟล์ whoami-pod-003.yml

จากไฟล์เราจะเห็นตัวอย่างมีการประกาศ evironment แบบใหม่คือ

1
2
3
4
5
- name: "CONFIG_DATABASE_FOR_CONNECTION"
valueFrom:
configMapKeyRef:
name: "basic-configmap"
key: "CONFIG_DATABASE"
valueFrom

ตรงนี้เป็นการบอกว่าค่า Value นั้นให้ไปเอามาจากไหนซึ่งตรงนี้ประกาศว่า configMapKeyRef ซึ่งแปลว่าเอามาจาก configMap ส่วนด้านในส่วน name , key นั้นเป็นการบอกว่า ConfigMap ชื่ออะไร และ key ที่จะให้เอาค่าคืออะไร จากตัวอย่างก็คือไปเอาค่าจาก ConfigMap ชื่อ basic-configmap และ key ชื่อ CONFIG_DATABASE

เอา ConfigMap ไป Mount volume เป็นไฟล์

ในตอนต้นเรารู้แล้วว่า Applicaiton นิยม Config ด้วยไฟล์ แต่ปัญหาเมื่อเอามาใช้กับ k8s คือมันต้องทำการ Mount volume ซึ่งก็จะมีปัญหาเรื่องอ่านเขียนได้จากกี่ Node เวลาแก้ไข Config ต้องไปแก้ที่ Volume ตรงไหน เพื่อแก้ปัญหานั้น k8s จึงทำให้เราสามารถ Mount ตัว ConfigMap ไปเป็น Volume ได้ โดยตัวอย่างนี้ผมจะสร้าง ConfigMap ที่มีค่าเป็นไฟล์ HTML และ Mount volume ไปใน nginx ซึ่งเมื่อเราเรียกผ่าน Browser จะต้องแสดงผลแบบไฟล์ HTML ของเรา

สร้างไฟล์ nginx-configmap.yml จากนั้นสั่ง apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-configmap
data:
indexFile: |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HELLO WORLD</title>
</head>
<body>
<h1>HELLO WORLD</h1>
</body>
</html>
wasineeFile: |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HELLO WASINEE</title>
</head>
<body>
<h1>HELLO WASINEE</h1>
</body>
</html>

จากไฟล์ที่เราประกาศจะเห็นว่าเราใช้เครื่อง “|” ซึ่งเครื่องหมายนี้เป็นการบอกว่าจะค่าที่จะ config นั้นมีหลายบรรทัด โดยในไฟล์นี้เราสร้าง configMap ที่มี 2 key คือ indexFile , wasineeFile

สร้างไฟล์ nginx-pod.yml จากนั้น apply ไฟล์

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80
volumeMounts:
- mountPath: /usr/share/nginx/html
name: nginx-volume
volumes:
- name: nginx-volume
configMap:
name: nginx-configmap
items:
- key: "indexFile"
path: "index.html"
- key: "wasineeFile"
path: "wasinee.html"
---
# ตรงนี้แค่ประกาศ Service เพื่อไว้ใช้เปิดเข้ามาดูผ่าน Browser ได้
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 32030
type: NodePort

จากนั้นเราเปิด Browser แล้วเข้า URL :

ซึ่งจะเห็นว่า nginx นั้นแสดงผลไฟล์ index.html เหมือนกับค่าใน configMap key : indexFile และ wasinee.html นั้นแสดงผลตรงกับค่าใน configMap key : wasineeFile

อธิบายไฟล์ nginx-pod.yml

ไฟล์นี้จะคล้ายๆไฟล์ nginx-use-pvc.yml แต่ต่างกันตรงการประกาศ volume

1
2
3
4
5
6
7
8
9
volumes:
- name: nginx-volume
configMap:
name: nginx-configmap
items:
- key: "indexFile"
path: "index.html"
- key: "wasineeFile"
path: "wasinee.html"

โดยอันนี้เราจะเปลี่ยนจาก persistentVolumeClaim มาเป็น configMap แทน

name

ตรงนี้เป็นการบอกว่าเราจะใช้ configMap ชื่ออะไรในการ Mount volume

items

ตรงนี้เป็นการบอกว่าจะเอา key ไหนไปเป็นไฟล์อะไรบ้างและเอาไปเป็นไฟล์วางไว้ในที่ไหน โดยจากตัวอย่าง เราจะเอา key: indexFile ไปเป็นไฟล์ index.html และ key : wasineeFile ไปเป็นไฟล์ wasinee.html

Secret คืออะไร

ถ้ามองแบบง่ายๆมันก็คือ ConfigMap นั่นแหละครับ แต่เป็น ConfigMap ที่มีการปกป้องไม่ให้สามารถอ่านออกได้แบบตรงๆ (ระดับง่ายสุดคือ base6
4) Secret เหมาะที่จะใช้กับอะไรที่ต้องการเป็นความลับ เช่น password , token , private key เป็นต้น (สามารถอ่านนิยามที่ถูกต้องได้ที่ : Link)

ลองสร้าง Secret

ทำการสร้างไฟล์ basic-secret.yml

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Secret
metadata:
name: basic-secret
type: Opaque
data:
username: d2FzaW5lZQ== # Base64-encoded username raw is : wasinee
password: UEBzc3cwcmQ= # Base64-encoded passwor raw is : P@ssw0rd

จากนั้นสั่ง apply และทำการ get และ describe ดู secret

จากภาพจะเห็นว่ามันแสดงผลคล้ายๆกับ configMap เลยคือมี key ให้รู้ว่า secret นี้มี key อะไรบ้าง เพียงแต่เราไม่สามารถเห็นค่า value : secret ได้เหมือนกับ configMap ก็เท่านั้น

อธิบายไฟล์ basic-secret.yml

ในส่วนของ apiVersion , kind , metadata ผมขอไม่พูดนะครับเพราะเหมือนกับ pod , deployment , configMap จะพูดตรง type ลงไป

type

ตรงนี้เป็นการบอกว่า Secret นี้เป็น type อะไร โดย Opaque เป็น type basic ของ secret ตัว type มีอีกมากมายสามารถดูได้ที่ https://kubernetes.io/docs/concepts/configuration/secret/#secret-types

data

ส่วนนี้เป็นการประกาศ key กับ value โดย value จะต้องเป็นค่าที่ถูก Base64 ไว้ครับ

เอา Secret ไปเป็น Environment

ในเมื่อ configMap สามารถเอาไปผูกเป็น Environment ได้ secret ก็น่าจะต้องเอาไปใช้เป็น Environment ได้

** ทำการลบ whoami-pod และ whoami-service ทิ้งก่อนนะครับ

สร้างไฟล์ whoami-pod-004.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
apiVersion: v1
kind: Pod
metadata:
name: whoami-pod
labels:
app: whoami
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
envFrom:
- secretRef:
name: basic-secret
---
# ส่วนนี้ประกาศ Service เฉยๆ
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
selector:
app: whoami
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 32020
type: NodePort

จากนั้นทำการ apply file และเปิด Browser แล้วเปิด URL : http://<ip ของเครื่องที่ run k8s>:32020/api?env=true (ของผมเป็น http://192.168.156.101:32020/api?env=true)

จากภาพจะเห็นว่าค่า username , password นั้นเอามาจาก secret: basic-secret

อธิบายไฟล์ : whoami-pod-004.yml

จากไฟล์จะเห็นว่าส่วนที่แตกต่างไปจากเดิมคือ envFrom

envFrom

ตรงส่วนนี้คือบอกว่าจะเอา env มาจากไหน โดย secretRef คือบอกว่าให้เอาจาก secretRef ซึ่งในตัวอย่างคือ secret ชื่อ basic-secret

1
2
3
envFrom:
- secretRef:
name: basic-secret

อยาก Set ค่าแค่บาง Environment จาก Secret ล่ะ

หัวข้อนี้เหมือนกับ configMap เลยคืออยาก set : environment แค่บางค่าจาก Secret ล่ะทำยังไง

** ทำการลบ whoami-pod และ whoami-service ทิ้งก่อนนะครับ

สร้างไฟล์ : whoami-pod-005.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
apiVersion: v1
kind: Pod
metadata:
name: whoami-pod
labels:
app: whoami
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
env:
- name: "OWNER"
value: "WASINEE"
- name: "CONFIG_DATABASE_PASSWORD"
valueFrom:
secretKeyRef:
name: "basic-secret"
key: "password"
---
# ส่วนนี้ประกาศ Service เฉยๆ
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
selector:
app: whoami
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 32020
type: NodePort

จากนั้นทำการ apply file และเปิด Browser แล้วเปิด URL : http://<ip ของเครื่องที่ run k8s>:32020/api?env=true (ของผมเป็น http://192.168.156.101:32020/api?env=true)

จากภาพจะเห็นว่าค่า Environment : CONFIG_DATABASE_PASSWORD มาจาก basic-secret ที่ key : password

อธิบายไฟล์ : whoami-pod-005.yml

จากตัวอย่างจะเห็นว่าที่แตกต่างจาก configMap คือ valueFrom ที่กำหนดเป็น secretKeyRef

secretKeyRef
1
2
3
4
5
- name: "CONFIG_DATABASE_PASSWORD"
valueFrom:
secretKeyRef:
name: "basic-secret"
key: "password"

จากตัวอย่างจะเห็นว่าเราบอกว่าเราจะเอาค่าจาก secret ชื่อ basic-secret ที่ key : password ไปเป็น environment ชื่อ CONFIG_DATABASE_PASSWORD

เอา Secret ไป Mount volume เป็นไฟล์

Secret นั้นแทบจะเหมือนกับ ConfigMap ซึ่งตัว ConfigMap สามารถ Mount volume ได้ ดังนั้น Secret ก็น่าจะต้อง Mount volume ได้เช่นกัน

** ทำการลบ nginx-pod และ nginx-service ทิ้งก่อนนะครับ

สร้างไฟล์ : nginx-use-secret.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80
volumeMounts:
- mountPath: /usr/share/nginx/html
name: nginx-volume
volumes:
- name: nginx-volume
secret:
secretName: basic-secret
items:
- key: "username"
path: "index.html"
---
# ส่วนนี้ประกาศ Service เฉยๆ
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 32030
type: NodePort

สั่ง apply ไฟล์ จากนั้นเราเปิด Browser แล้วเข้า URL : http://<ip ของเครื่อง run k8s>:32030/index.html (ของผมเป็น http://192.168.156.101:32030/index.html)

ซึ่งจากภาพจะเห็นว่าจะได้คำว่า wasinee โผล่ขึ้นมาที่หน้าจอ ซึ่งตรงกับค่า basic-secret ที่ key : username

อธิบายไฟล์ : nginx-use-secret.yml

จากไฟล์จะเห็นส่วนที่แตกต่างจาก configMap คือตรงประกาศ volume นั้นประกาศด้วย secret

secret
1
2
3
4
5
6
7
volumes:
- name: nginx-volume
secret:
secretName: basic-secret
items:
- key: "username"
path: "index.html"

จากตัวอย่างจะเห็นว่าเราประกาศบอกว่า volume นี้เอามาจาก secret โดยบอกว่าเอามาจาก secretName ชื่อ basic-secret และเอา key username ไปเป็นไฟล์ index.html ดังนั้นค่า wasinee ที่อยู่ที่ key : username เลยไปกลายเป็นไฟล์ index.html อย่างที่เราทดลอง

สรุป

สำหรับตอนนี้เราได้รู้ว่า ConfigMap กับ Secret คืออะไร ได้รู้วิธีการประกาศ การนำไปใช้เป็น environment และนำไป mount volume เป็นไฟล์ สำหรับตอนต่อไปเราจะไปดูเกี่ยวกับ ingress กันครับ

Basic Kubernetes Part 4 - สร้าง Volume ให้ Pod ใช้

Basic Kubernetes Part 4 - สร้าง Volume ให้ Pod ใช้

3 ตอนที่ผ่านมาเราได้เรียนรู้การสร้าง Pod สร้าง Network เข้าไปหา Pod สิ่งที่เรายังขาดคือการ Disk หรือ Volume ที่ให้ตัว Pod (Application) ของเราใช้งานในการเขียนไฟล์ อ่านไฟล์ วางไฟล์ config ตัวอย่างของ Pod (Application) ที่จำเป็นต้องใช้ Database ที่ต้องใช้ Disk หรือ Volume ในการเก็บข้อมูล

ตัวอย่างไฟล์ Configuration ทั้งหมดในตอนนี้อยู่ที่ Link

Persistent Volume : PV

Persistent Volume หรือเรียกสั้นๆว่า PV นั้นคือ Volume ที่เราแจ้งให้ k8s ทราบว่า k8s สามารถเอาไปใช้งานได้ เหมือนกับเราต้องการเอา Hard disk (สมัยนี้อาจจะเป็น SSD หมดแล้ว) มาใช้งาน เราก็ต้องแจ้งกับ OS ว่า Hard disk ของเราต่ออยู่กับช่องไหน

ลองสร้าง Persistent Volume กัน

ทำการสร้าง Folder ชื่อ nginx-volume จากนั้นสร้างไฟล์ index.html ขึ้นมา

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HELLO WORLD</title>
</head>
<body>
<h1>HELLO WORLD</h1>
</body>
</html>

สร้างเสร็จแล้วจะได้โครงสร้าง Folder แบบนี้

1
2
3
.
├── nginx-volume
└── index.html

ออกมาจาก Folder nginx-volume จากนั้นสร้างไฟล์ basic-pv.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: PersistentVolume
metadata:
name: nginx-pv
spec:
storageClassName: nginx-volume
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
hostPath:
path: <"Path ไป Folder nginx-volume">

# ตัวอย่าง
# ของผมว่าง nginx volme อยู่ที่ /root/test-pod/part4/nginx-volume ข้อมูลเลยเป้นด้านล่าง
# path: /root/test-pod/part4/nginx-volume

จากนั้นสั่ง apply ตัวไฟล์ basic-pv.yml จากนั้นสั่งด้านล่างเพื่อดูรายละเอียดของ Persistent Volume

1
2
kubectl get pv
kubectl descrbie pv nginx-volume

อธิบายไฟล์ basic-pv.yml

ในส่วนของ apiVersion , kind , metadata ขอไม่อธิบายนะครับ เพราะเหมือนกับ pod , service , deployment จะขออธิบายในส่วนของ spec

storageClassName

ส่วนนี้เป็นการบอกว่า volume นี้อยู่ในประเภทไหน โดย storageClassName จะถูกเอาไปใช้ใน map เพื่อเอา volume ไปใช้งาน ซึ่งค่านี้สามารถตั้งเป็นอะไรก็ได้ ผมตั้งชื่อมันว่า nginx-volume

capacity

เป็นการบอกว่า Volume นี้มีขนาดเท่าไหร่ จากตัวอย่างของผมประกาศไว้ว่ามีขนาด 2 GB

accessModes

ส่วนนี้เป็นการบอกว่า Volume นี้สามารถเข้าถึงจาก Node ใน k8s cluster ( k8s สามารถทำให้มีหลายๆเครื่องทำงานร่วมกันได้ ดังนั้น 1 cluster สามารถมีหลายเครื่องทำงานร่วมกันได้ โดยเราจะเรียกเครื่องใน k8s cluster ว่า Node ) รูปใดบ้าง โดยตอนนี้มี 3 แบบ

  • ReadWriteOnce : สามารถอ่านเขียนได้จาก Node เดียวใน k8s cluster

  • ReadOnlyMany : สามารถอ่านได้อย่างเดียวจากทุก Node ใน k8s cluster

  • ReadWriteMany : สามารถอ่านเขียนได้จากทุก Node ใน k8s cluster

ตัวอย่างเรา Set เป็น ReadWriteOnce

hostPath

ส่วนนี้เป็นการบอกว่า Volume นี้เป็นแบบใช้ไฟล์บนเครื่อง Node นั้นโดยบอกว่า path บนเครื่องคือ path ไหน โดยในตัวอย่างของผมคือ /root/test-pod/part4/nginx-volume

Persistent Volume Cliam : PVC

ในส่วนของ Persistent Volume นั้นเป็นประกาศให้ k8s ทราบว่ามี Volume พร้อมให้ใช้งาน แต่คงมีปัญหาแน่นอนถ้ามีหลายที่พยายามใช้ Volume เดียวกัน เช่น Volume นี้ถูกใช้งานจาก MYSQL และ MongoDB ดังนั้นเพื่อให้ Volume ถูกเอาไปใช้งานและไม่เกิดแย่งกันจึงต้องมีการประกาศสิ่งที่เรียกว่า Persistent Volume Cliam เพื่อบอกว่า Volume ตรงนี้ชั้นจองไว้นะ

ลองสร้าง Persistent Volume Cliam : PVC

ทำการสร้างไฟล์ basic-pvc.yml

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: nginx-volume
resources:
requests:
storage: 2Gi

จากนั้นสั่ง apply ไฟล์ basic-pvc.yml และสั่งดูรายละเอียดของ Persistent Volume claim

1
2
kubectl get pvc
kubectl describe pvc nginx-pvc

ลองสังเกตที่วงแดงไว้จะเห็นว่าตัว PVC นี้อยู่ในสถานะ Bound และส่วนตรง Volume มีค่าเป็น nginx-pv ซึ่งสองค่านี้เป็นการบอกว่า PVC ตัวนี้ทำการผูกกับตัว PV แล้ว

หากคุณลองสั่ง Describe ตัว nginx-pv จะเห็นว่ามีการเปลี่ยนไป โดยลองดูภาพด้านล่าง ซึ่งจะเป็นไปในทางเดียวกันคือ nginx-pv ถูก nginx-pvc ทำการเอาไปใช้แล้ว

อธิบายไฟล์ basic-pvc.yml

accessModes

ส่วนนี้เหมือนกับ PV เลย แต่แค่เปลี่ยนจาก PV เป็น PVC ซึ่งตัว PVC จะเอา accessModes นี้ไปหา PV ที่มี accessModes รองรับความต้องการ PVC

storageClassName

ส่วนนี้เป็นการบอกว่า PVC นี้จะใช้ storageClass ไหน

resources

ส่วนนี้เป็นการบอกว่า PVC ต้องการ Resource อะไรบ้าง จากตัวอย่างบอกว่าต้องการ Storage size 2Gi

ลองสร้าง PVC ที่หา PV ไม่ได้

ทำการสร้างไฟล์ unmatch-pvc.yml

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: unmatch-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: wasinee-volume
resources:
requests:
storage: 1Gi

จากนั้นทำการ apply file และ describe ดูตัว unmatch-pvc

จะเห็นว่าตัว unmatch-pvc นั้นไม่สามารถ Match ได้กับ PV ไหนได้ โดยด้านล่างจะมี message บอกว่าไม่สามารถหา storageClassName wasinee-volume ได้ ซึ่งก็แน่นอนอยู่แล้วเพราะเราไม่ได้สร้าง PV ที่มี storageClassName นี้ขึ้นมา

สร้าง Pod ที่ใช้งาน PVC

สร้างไฟล์ nginx-use-pvc.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80
volumeMounts:
- mountPath: /usr/share/nginx/html
name: nginx-webpage
volumes:
- name: nginx-webpage
persistentVolumeClaim:
claimName: nginx-pvc
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 32010
type: NodePort

สั่ง apply ไฟล์ nginx-use-pvc.yml จากนั้นลองเปิด browser แล้วไปที่ url : http://<ip เครื่องที่ run k8s>:32010 โดยของผมจะเป็น http://192.168.156.101:32010

จะเห็นว่า Browser แสดงหน้า Html ออกมาเป็นเหมือนไฟล์ index.html ที่เราสร้างในตอนต้น จากนั้นให้เราลองแก้ไฟล์ index.html ของเราเป็นอย่างอื่นดู อย่างของผมเปลี่ยนจาก HELLO WORLD เป็น HELLO WASINEE แล้วไปเปิด Browser ใหม่จะเห็นว่าจะแสดงผลเปลียนไป

จากการทดลองจะเห็นว่าตัว pod nginx ที่เราสร้างนั้นมีการใช้ไฟล์ index.html ที่เรา mount เข้าไปแล้ว ซึ่งเท่ากับว่าเราสามารถ Mount ตัว Volume ให้กับ Pod ได้เรียบร้อยแล้ว

อธิบายไฟล์ nginx-use-pvc.yml

ในส่วนของ service และ pod ที่ใช้ keyword เก่าจะไม่ขอพูดถึงนะครับ จะพูดถึงส่วนใหม่ที่เพิ่มเข้ามา

volumes

1
2
3
4
5
6
spec:
containers: ....
volumes:
- name: nginx-webpage
persistentVolumeClaim:
claimName: nginx-pvc

ตรง volumes นี้จะเป็นการบอกว่าตัว Pod นี้มี Volumes อะไรบ้าง (มีได้หลาย Volume) และ Volume แต่ละตัวนั้นไปเอามาจากไหน จากในตัวอย่างคือมี volume ชื่อ nginx-webpage โดย Volume นี้ไปเอามาจาก persistentVolumeClaim โดยมีชื่อว่า nginx-pvc

VolumeMounts

1
2
3
4
5
6
7
8
containers:
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80
volumeMounts:
- mountPath: /usr/share/nginx/html
name: nginx-webpage

ตรง VolumeMounts ตรงนี้จะเป็นการบอกว่า container นี้จะทำการ Mount volumes อะไรบ้าง (ใส่ได้หลาย mount ) โดยจากตัวอย่างนั้นเราจะบอกว่าเราจะทำการ Mount volume เข้าไปใน container ที่ mountPath : /usr/share/nginx/html (Path นี้คือ Path ที่เอาไว้เอาไฟล์จำพวก web มาใส่เพื่อให้ตัว nginx ทำการแสดงผล) โดย map กับ Volume ของ Pod ที่ชื่อ nginx-webpage ซึ่ง nginx-webpage ไปเอา volume มาจาก pvc ชื่อ nginx-pvc ซึ่ง nginx-pvc นั้นคือ folder nginx-volume ที่เราสร้างกันตอนแรก

ลองลบ PVC กับ PV กัน

เมื่อสร้างแล้วก็ต้องลบได้ ดังนั้นเรามาลบ PVC กับ PV กัน โดยเราลองลบ PV กันก่อนโดยสั่ง

1
kubectl delete pv nginx-pv

โดยเมื่อกดสั่งจะเห็นว่ามันค้างไปและเมื่อ get pv มาดูจะพบว่ามันอยู่ในสถานะ Terminating (กำลังปิด) ซึ่งจะค้างอยู่อย่างนั้นไปเรื่อยๆ ซึ่งทำไมล่ะ ก็ง่ายๆครับเพราะ PV นี้มีคนใช้อยู่ ซึ่งนั่นก็คือ PVC : nginx-pvc นั่นเอง ก็เลยค้างอยู่อย่างนั้น

ดังนั้นเราก็ต้องไปลบ nginx-pvc ก่อน

1
kubectl delete pvc nginx-pvc

ซึ่งพอเราลบก็จะพบว่ามันค้างและเมื่อกดออกมาและมา get pvc ดูจะพบว่าติดสถานะ Terminating เหมือนกัน ซึ่งก็น่าจะเดากันได้ที่ติดอยู่ก็เพราะมี Pod สัก Pod กำลังใช้งาน pvc อยู่ ซึ่งนั่นก็คือ Pod : nginx-pod ดังนั้นถ้าเราจะลบ PVC ได้ก็ต้องลบ Pod ทิ้งก่อน

1
kubectl delete -f nginx-use-pvc.yml

ซึ่งเมื่อเราสั่งลบตัว nginx-pod ทิ้งไปแล้วไป get pv กับ pvc จะพบว่า nginx-pv และ nginx-pvc หายไป

ลองสร้าง Database : MariaDB ผ่าน Kube มาใช้งานกัน

เพื่อทดสอบความเข้าใจและดูว่ามันเอาไปใช้งานจริงยังไง เรามาลองสร้าง Database บน k8s กัน

สร้าง PV กับ PVC กัน

ในขั้นแรกให้สร้าง Folder ขั้นมาสัก Folder เพื่อเอาไว้ใช้เก็บข้อมูลของ database จากนั้นสร้าง PV กับ PVC ขั้นมา ลองทำเองกันดูครับ ของผมที่ทำจะได้แบบข้างล่าง

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: v1
kind: PersistentVolume
metadata:
name: database-pv
spec:
storageClassName: database-volume
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
hostPath:
# อันนี้แล้วแต่ว่าคุณจะเอา mount ข้อมูลไว้ที่ไหน ของผม Mount ไว้ตามนี้
# อาจจะติดเรื่องสิทธิ์ ผมแนะนำเปลี่ยนเป็นให้สิทธิ์หมดก่อน เพื่อใช้ในการทดลอง
path: /root/k8s-data/database-volume
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: database-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: database-volume
resources:
requests:
storage: 2Gi

สร้าง MariaDB เพื่อใช้งาน

ขั้นต่อมาคือการทำการสร้างตัว Pod MariaDB ขึ้นมาและสร้าง Service ขึ้นมา ผมแนะนำให้ลองสร้างกันเองดูก่อน ส่วนของผมสร้างได้แบบข้างล่าง โดยผมใช้การสร้าง Pod ผ่าน Deployment (ในการใช้งานจริงเราจะสร้าง Pod ผ่าน Deployment กัน ที่ผมสร้าง Pod ตรงๆเพราะอยากให้เข้าใจง่าย) ในส่วนของการ Mount volume นั้น เรา Mount ที่ Path : /var/lib/mysql ซึ่งเป็น Path ที่ Mariadb ใช้ในการเก็บข้อมูล Database

สุดท้ายคือส่วนของ Service อันนี้ก็คือทำการเปิดช่องทาง Client สามารถติดต่อกับ Port ได้โดยยิงเข้ามาผ่าน IP เครื่อง + Port ที่กำหนดไว้ (ของผม IP : 192.168.156.101 Port : 32110)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
apiVersion: apps/v1
kind: Deployment
metadata:
name: database-pod
labels:
app: database-maria
spec:
selector:
matchLabels:
app: database-maria
template:
metadata:
labels:
app: database-maria
spec:
containers:
- name: database-maria
imagePullPolicy: Always
image: mariadb:10.10
env:
- name: MARIADB_ROOT_PASSWORD
value: root
ports:
- containerPort: 3306
name: database-port
volumeMounts:
- mountPath: /var/lib/mysql
name: database-volume
volumes:
- name: database-volume
persistentVolumeClaim:
claimName: database-pvc
---
apiVersion: v1
kind: Service
metadata:
name: database-service
spec:
selector:
app: database-maria
ports:
- protocol: TCP
port: 3306
targetPort: 3306
nodePort: 32110
type: NodePort

ลองใช้งานกัน

ให้ลองใช้ตัว SQL Query Browser ต่อเข้า Database ที่พึ่งสร้าง ของผมใช้ตัว dbeaver เพื่อทดสอบว่าสามารถเชื่อมต่อเข้ากับ Database ได้หรือไม่ ซึ่งผมสามารถติดต่อได้ดังภาพ

ลองไปดูที่ Path ที่ Mount volume

ลองไปดูที่ path ที่ Mount volume กันว่ามีไฟล์อะไรโผล่ขึ้นมาไหม ซึ่ง Path ที่ Mount volume ของผมคือ /root/k8s-data/database-volume ซึ่งเมื่อเข้าไปดูก็พบไฟล์มากมายที่เกิดจากตัว MariaDB

เราต้องสร้าง PV เองตลอดเลยไหม

ตัวอย่างที่ผมได้แสดงให้ดูเนี่ยจะเห็นว่าเราต้องสร้าง PV เองเสมอทุกครั้ง แต่ในโลกความเป็นจริงนั้น จะมีสิ่งที่เรียกว่า Dynamic Volume Provisioning คอยหา Volume มา Map กับ PVC ให้โดยอัตโนมัติ (ในการทดลองเราไม่มีเพราะต้อง Config เอง ยุ่งยากมาก) โดยเราแค่สร้างตัว PVC แล้วกำหนด storageClassName ให้ตรงกับที่ Dynamic Volume Provisioning กำหนดให้ พอสร้าง PVC ตัว Dynamic Volume Provisioning จะสร้าง Volume แล้ว Map ให้เอง

ตัวอย่างของ Dynamic Volume Provisioning

เรื่องที่ควรไปอ่านเพิ่ม

เนื่องจากบทความนี้เป็นบทความสอนใช้งานเบื้องต้น ถ้าลงรายละเอียดเยอะจะทำให้ยุ่งยากและคิดว่ามันเป็นเรื่องจากที่จะใช้ ดังนั้นผมจึงอธิบายแค่เรื่องง่ายๆกับทำยังไงให้สร้างสิ่งที่ต้องการและใช้งานได้ ซึ่งแน่นอนว่าในการใช้งานจริงระดับ Production ยังมีเรื่องที่ควรจะต้องเข้าไปอ่านทำความเข้าใจเพิ่ม โดยเรื่องที่แนะนำให้ไปอ่านเพิ่มคือ

  • Lifecycle of a volume and claim : ส่วนนี้จะพูดถึงสถานะต่างๆของ volume และ cliam ว่ามีอะไรบ้าง ถ้าลบ Cliam แล้วอยากใช้ Volume ต่อต้องทำอย่างไร มีแบบไหนบ้าง เป็นต้น

  • Dynamic Provisioning : อันนี้พูดถึงเรื่อง Dynamic Provisioning ว่าทำงานอย่างไร

  • Types of Persistent Volumes : ส่วนนี้เป็นชนิดของ Persistent Volumes ในตัวอย่างที่เราทำกันเราใช้ pv type : hostPart แต่จริงๆแล้วยังมี type อื่นอีกมากมายที่เราสามารถใช้ได้

สรุป

สำหรับตอนนี้เราได้รู้วิธีจะ Mount volume เข้าไปใน Pod ได้ยังไง ได้รู้ว่า Persistent Volume (PV) และ Persistent Volume (PVC) คืออะไร ได้ลองสร้าง Database : MariaDB ขึ้นมาใช้งาน ซึ่งถ้าคุณเข้าใจและได้ลองทำ คุณก็สามารถ Deploy Appplication ขึ้นไปใช้งานบน k8s ได้แล้ว ในตอนต่อไปเราจะมาพูดถึง ConfigMap และ Secret ของ k8s กันว่าคืออะไร

Basic Kubernetes Part 3 - Service คืออะไรมีไว้ใช้ทำอะไร

Basic Kubernetes Part 3 - Service คืออะไรมีไว้ใช้ทำอะไร

เราจะยิง Request ไปหา Pod ยังไง

ในตอนที่แล้วเราได้รู้แล้วว่า pod เปรียบเสมือนเครื่องที่ลง docker ที่สามารถ Run container ได้ ซึ่งก็แปลว่าเรามีเครื่องที่ Run Application ที่เราต้องการแล้ว แต่ปัญหาคือเราไม่สามารถยิง Request ไปหา pod แต่ละเครื่องได้แบบง่ายๆ เพราะ pod ถูกกำหนด IP ให้แบบอัตโนมัติ อีกทั้งเมื่อเรา Scale out pod ให้มีหลายตัวก็แปลว่า pod ที่ Run application ของเรามีหลายเครื่องที่ Run เหมือนกัน เราจะรู้ได้อย่างไรว่าเครื่องไหนเป็นที่เราอยากจะยิง Request ไปหาดังปัญหาในภาพ

จากภาพจะเห็นว่ามี Pod 6 ตัวคือ

  • client : 1 ตัว
  • nginx : 3 ตัว
  • whoami : 2 ตัว

ถ้า client ต้องการติดต่อไปหา nginx ตัว client จะรู้ได้ยังไงว่าจะยิงไป IP ไหน ( อย่าลืมว่า client ไม่รู้อะไรเลยนะครับ ) ถ้าเราคิดง่ายๆก็ให้ client describe pod ทุกตัวใน k8s เพื่อดูว่า pod ไหนเป็น pod nginx จากนั้นก็เอาไปเก็บไว้ในตารางของ client หลังจากนี้เวลาจะยิงก็ไปดูจากตาราง

ดูแล้วก็เป็นวิธีที่เป็นไปได้แต่ปัญหาคือ

  1. ถ้า k8s ทำการเพิ่มลดจำนวน pod เช่นสั่งเพิ่ม pod เป็น 10 ตัว หรือ ลดเหลือ 1 ตัว ตาราง ip ที่ client มีก็ไม่ใช่ตาราง ip ที่ถูกต้อง ทางแก้ก็คืออาจจะยิงไปถามบ่อยๆเอาก็ได้ แต่ต้องบ่อยแค่ไหนล่ะ บ่อยไปก็สร้างปัญหาให้ k8s ไม่บ่อยข้อมูล ip ก็ไม่ถูกต้อง

  2. การที่ทำแบบนี้แปลว่า k8s ต้องเปิดให้ client สามารถยิงมาถามว่า pod ใน k8s มีอะไรบ้าง และ เป็น pod อะไร การที่เปิดให้ทำอะไรแบบนี้ก็ดูเสี่ยงเพราะกลายเป็นว่า client สามารถรู้ได้ว่ามี pod อะไรบ้างใน k8s ถ้าสัก pod ใน k8s โดนโจมตี เขาก็สามารถโจมตีทุก pod ใน k8s ได้เลย

ในอีกแนวทางนึงถ้าเรามีอะไรสักอย่างโผล่ขึ้นมาแล้วเรายิงไปที่นั่นแล้วอะไรสักอย่างนั้นสามารถยิงไปหา pod ที่ถูกต้องได้ (จริงๆก็คือ Reverse proxy นั่นเอง) ถ้าเรามีอะไรสักอย่างนั้นเราก็แค่ยิงไปที่นั่นแล้วที่นั่นก็จะส่ง Request ไปหา nginx ต่อให้ ซึ่งมันจะทำงานดังภาพ

สิ่งนั้น ที่นั่น ไอนั่น ที่ผมพูดถึงก็คือ Service ของ k8s นั่นเอง

Service คืออะไร

คุณสามารถดู Configuration file ทั้งหมดได้ที่ :Link

ถ้าพูดกันแบบง่ายๆ Service คือตัวที่ทำให้เราสามารถ Request เข้าไปหา Pod ได้ โดยไม่ไปคอย Describe ดู IP ของ Pod ตัวนั้นว่าอยู่ที่ไหน แค่ยิงไปหา Service เดี๋ยว Service จะพา Request ไปหา Pod ให้เอง (นิยามที่ผมพูดไม่ถูกต้อง 100% ถ้าถูกต้องจริงๆดูได้ที่ https://kubernetes.io/docs/concepts/services-networking/service/)

สร้าง Service กัน

ก่อนที่จะทดลองสร้าง Service ผมอยากขอให้ทุกท่านลบ Deployment และ Pod ที่มีอยู่ทิ้งก่อน เพื่อให้การทดลองไม่มีผลกระทบจาก Pod ตัวก่อนๆที่เคยสร้าง โดยใช้คำสั่งด้านล่างเพื่อทำการลบ

1
2
kubectl delete deployment --all
kubectl delete pod --all

จากนั้นสร้างไฟล์ test-server-pod.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
owner: wasinee
spec:
containers:
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
name: whoami-pod
labels:
app: whoami
owner: wasinee
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80

โดยไฟล์นี้จะทำการสร้าง Pod ขึ้นมา 2 ตัวคือ nginx กับ whoami จากนั้นสั่งคำสั่งด้านล่างเพื่อสร้าง pod ขึ้นมา

1
kubectl apply -f test-server-pod.yml

จากนั้นสร้างไฟล์ client-pod.yml

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Pod
metadata:
name: client-pod
labels:
app: busybox
spec:
containers:
- name: busybox
image: yauritux/busybox-curl
command: ["tail", "-f", "/dev/null"]

จากนั้นสั่งสร้าง pod ด้วยคำสั่ง

1
kubectl apply -f client-pod.yml

ซึ่งเมื่อสั่งเสร็จเราจะมีข้อ Pod ทั้งหมด 3 ตัวดังภาพ

จากนั้นทำการสั่ง Describe nginx-pod กับ whoami-pod เพื่อเอา IP ของ Pod ทั้ง 2 ซึ่งของผมคือ

  • nginx-pod : 10.42.0.103
  • whoami-pod : 10.42.0.104

จากนั้นทำการสั่ง

1
kubectl exec -ti client-pod sh

อธิบายคำสั่ง

  • kubectl exec เป็นการสั่งคำสั่งที่อยู่ใน pod
  • -ti คือการเชื่อมต่อ terminal ของ pod เข้ากับ terminal ของเรา
  • kubectl exec -ti client-pod sh รวมแล้วคือสั่งคำสั่ง sh ในเครื่อง pod และเชื่อมต่อ terminal ของเราเข้ากับ pod

ดังนั้นจะกลายเป็นว่าเราได้คุมคำสั่ง sh ในเครื่อง pod ซึ่งแปลว่าเราสามารถสั่ง command ต่างๆในเครื่อง pod ได้ทั้งหมด ซึ่งการที่เราทำอย่างนี้ก็เพื่อจำลองว่าเราเป็นเครื่อง client ที่จะยิงไปหา pod เครื่องอื่นๆ

ลองยิงไปหา pod เครื่องอื่นๆด้วยคำสั่งด้านล่าง

1
2
curl 10.42.0.103
curl 10.42.0.104

จากภาพจะเห็นว่าเราสามารถยิงไปหา nginx และ whoami ได้ จากนั้นลองสั่ง exit เพื่อออกจาก pod client-pod

สร้าง Service มาทดลองใช้งาน

ทำการสร้างไฟล์ service-1-basic.yml จากนั้นทำการสั่ง kubectl apply เพื่อสร้าง service

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP

จากนั้นสั่ง get service และทำการ describe service nginx-service เพื่อดูรายละเอียด

1
2
kubectl get service
kubectl describe service nginx-service

คราวนี้เราลองเข้าไปในเครื่องของ client-pod ด้วยคำสั่ง kubectl exec และสั่ง curl ไปที่ IP ของ Service ซึ่งของผมคือ 10.43.19.168 (ของคุณอาจไม่เหมือนกับของผม)

1
curl 10.43.19.168

จะเห็นว่าเราได้ Response เหมือนกับที่ยิงไปที่ 10.42.0.103 (nginx-pod) เลย ซึ่งนั่นก็แปลว่า เราสามารถยิงไปหา pod ผ่านการยิงไปที่ service ได้

คราวนี้เราลองมาดูรายละเอียดของ Service อีกรอบ

สังเกตตรงค่า Endpoint ของ Service Endpoint นี้คือ Endpoint ที่ Service นี้ชี้ไปหา ซึ่งค่าของผมมันคือ 10.42.0.103 ซึ่งมันคือ IP ของ nginx-pod นั่นก็แปลว่า Service นี้ชี้ไปหา nginx-pod ดังนั้น การยิงไปหา Service นี้ก็คือการยิงไปหา nginx-pod นั้นเอง

คราวนี้คุณอาจสงสัยว่า Service รู้ได้อย่างไรว่า Pod ไหนเป็น Pod nginx ขอให้ดูตรง Selector ครับ จะเห็นว่าค่าคือ

1
app=nginx

คุ้นๆค่านี้ไหมครับ ลองดูที่ label ของ nginx-pod ดูครับว่ามันคือค่าอะไร จะเห็นว่ามันคือค่าเดียวกันครับ ดังนั้น Service ใช้ตัว selector ไปหา label ของ pod ที่ตรงกับค่า selector มาเป็น Endpoint ของ Service นี้ครับ

อธิบายไฟล์ service-1-basic.yml

ส่วนของ apiVersion , kind , metadata ผมไม่ขอพูดนะครับ เนื่องจากเหมือนกับของ pod และ deployment จะขออธิบายในส่วนของ spec เป็นต้นไป

selector

ค่านี้เป็นค่าที่กำหนดว่าจะเอา pod ตัวไหนมาเป็น endpoint ที่ service ชี้ไปบ้าง โดยเอาไปหา pod ที่มีค่า label ตรงกับ selector

จากตัวอย่างไฟล์ service-1-basic.yml มีค่า selector คือ app=nginx ดังนั้น pod ทุกตัวที่มีค่า label app=nginx ซึ่งมี pod ที่มีค่า label app=nginx คือ nginx-pod

ports

ส่วนนี้เป็นการบอกว่า Service นี้เปิดที่ Port อะไรบ้าง สังเกตุว่า port มี s ดังนั้นสามารถกำหนดได้หลาย port

  • protocol ส่วนนี้เป็นการบอกว่า protocol ของ port นี้คืออะไร
  • port: port ของ service ที่เปิดคืออะไร
  • targetPort: port ของ endpoint เป้าหมายคือ port อะไร

type

อันนี้เป็นชนิดของ Service ส่วนนี้ขออธิบายทีหลัง

ทดลอง Selector เพื่อความเข้าใจ

สร้างไฟล์ service-2-test-selector.yml ด้วยข้อมูลข้างล่างจากนั้นสั่ง apply และ describe ตัว service

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: wasinee-service
spec:
selector:
owner: wasinee
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP

จากภาพเราจะเห็นว่า selector ของเราคือ owner=wasinee ซึ่ง pod ที่มี label ดังกล่าวมี 2 pod คือ nginx-pod กับ whoami-pod ซึ่งนั่นทำให้ตัว Endpoint ของ Service มี 2 IP คือ 10.42.0.103 (nginx-pod) , 10.42.0.104(whoami-pod)

ซึ่งถ้าเรา kubectl exec เข้า client-pod แล้วลองยิงไปที่ wasinee-service ( 10.43.123.125 IP ของคุณไม่เหมือนของผม ) บางครั้งเราจะได้ response ของ nginx-pod บางครั้งเราจะได้ response ของ whoami-pod ดังภาพ

ดังนั้น seletor ของ Service และ label ของ pod เป็นสิ่งสำคัญมาก ดังนั้นตอนสร้าง Pod ก็ควรเตรียม label ไว้ให้ Service ชี้มา และ selector ก็ควรกำหนดให้ตรงกับ pod ที่ต้องการจริงๆ

ทดลอง ports เพื่อความเข้าใจ

สร้างไฟล์ service-3-test-port.yml จากนั้น apply และ describe ตัว test-port-service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: v1
kind: Service
metadata:
name: test-port-service
spec:
selector:
app: nginx
ports:
- name: port-a
protocol: TCP
port: 80
targetPort: 80
- name: port-b
protocol: TCP
port: 81
targetPort: 80
- name: port-c
protocol: TCP
port: 82
targetPort: 81

type: ClusterIP

ในการ test นี้เราสร้าง service ที่เปิด port 3 port คือ 80, 81, 82 จากนั้นเรามาลองยิงไปที่ service ตาม port เหล่านี้แล้วมาดูผลลัพธ์กันว่าเป็นอย่างไร ทำไมถึงเป็นอย่างนั้น

ยิงไป port 80

การยิงไปที่ port 80 ของ service จากนั้นตัว service ส่งต่อ request ไปที่ pod ที่ port 80 (targetPort) ซึ่งการยิงแบบนี้ทำให้ได้ response จาก nginx-pod กลับมา

ยิงไป port 81

การยิงไปที่ port 81 ของ service จากนั้นตัว service ส่งต่อ request ไปที่ pod ที่ port 80 (targetPort) ซึ่งการยิงแบบนี้ทำให้ได้ response จาก nginx-pod กลับมา

ยิงไป port 82

การยิงไปที่ port 82 ของ service จากนั้นตัว service ส่งต่อ request ไปที่ pod ที่ port 81 (targetPort) ซึ่งเมื่อยิงไปที่เครื่อง nginx-pod แล้วตัว nginx-pod ไม่ได้เปิด port 81 ดังนั้น ยิงไปจึงได้รับ Error เหมือนดังภาพ

จากการทดลองในส่วนนี้น่าจะทำให้เราเข้าใจเรื่อง port ของ service นะครับว่า port คืออะไร targetPort คืออะไร ตั้งค่ายังไงให้เหมาะสมกับงานที่เจอ

เรียก service ด้วยชื่อ

หลังจากทดลองกันมานานคุณอาจจะสงสัยว่า แล้วมันต่างจากการหา ip ผ่าน pod ยังไงวะ เพราะสุดท้ายก็ต้องไป describe service เอา ip มาอยู่ดี ที่ผมทำไปทั้งหมดเนี่ยเพื่อให้ง่ายในการทดลอง ในความเป็นจริงนั้นเวลาเราจะเรียก service นั้นเราจะเรียกด้วยชื่อครับ โดยชื่อ service นั้นมีหลักการตั้งแบบนี้

1
<serviceName>.<namespaceName>.svc.cluster.local
  • serviceName คือชื่อ service ที่เราต้องการยิงไปหา
  • namespaceName คือชื่อ namespace ที่ service นั้นอยู่ ( ตอนนี้เรายังไม่ได้กำหนด namespace ดังนั้น namespace ของเราคือ default )

สมมติถ้าเราจะยิงไป service : nginx-service เราจะต้องยิงไปที่

1
nginx-service.default.svc.cluster.local

ด้วยวิธีนี้เราก็ไม่จำเป็นต้องไปหา IP จากการ describe service เพื่อดู ip แค่เรารู้ชื่อ service กับ namespace เพียงเท่านี้ก็สามารถยิงไปหา service ที่ต้องการได้แล้ว

เครื่อง Client ที่อยู่ข้างนอกจะยิงเข้ามายังไง

ที่เราทดลองไปทั้งหมดนั้น client ที่เราทดลองอยู่ภายใน k8s แต่ถ้า client อยู่นอก k8s แล้ว เราจะยิงเข้าไปได้ยังไง

เราลองจำลองตัวเองเป็น client ที่อยู่นอก k8s สิ่งที่ client รู้เกี่ยวกับ app มีเพียงอย่างเดียวคือ เครื่องที่ run k8s อยู่มี IP อะไร ( ของผมคือ 192.168.156.101 )

คราวนี้โดยปกติแล้วเวลา 2 เครื่องติดต่อกันในระดับ Application layer นั้นจะต้องระบุ IP และ Port ดังนั้น ถ้าเราสามารถ Map port ไปหา Service ของ k8s ได้ เราก็สามารถยิงจากด้านนอก k8s เข้าไป pod ที่อยู่ใน k8s ได้ ซึ่ง k8s ก็ได้เปิดช่องทางนี้ให้กับเราโดยสร้าง Service type ชนิด NodePort ขึ้นมาให้กับเรา

ลองสร้าง ServiceType : NodePort

สร้างไฟล์ : service-4-test-node-port.yml จากนั้นสั่ง kubeclt apply และสั่ง kubectl describe ดูรายละเอียด

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: test-node-port-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 32010
type: NodePort

จะเห็นว่ามีกันเพื่ม NodePort 32010 ตาม config nodePort ที่เพิ่มเข้ามา และ Type เปลี่ยนเป็น NodePort ตามใน Config ไฟล์

คราวนี้ให้เราเปิด Browser ขึ้นมาและเปิดไปที่ url : http://<ip เครื่องที่ run k8s>:32010 โดยของผมเครื่อง run คือ 192.168.156.101 url จึงเป็น http://192.168.56.101:32010

ซึ่งเมื่อลองยิงไปจะเห็นว่าเราจะได้รับ Response กลับมาจาก nginx-pod ซึ่งนั่นทำให้เห็นว่าเราสามารถเชื่อมต่อกับ nginx-pod ผ่าน Service ที่เราพึ่งทำขึ้นมาได้ โดยเชื่อมต่อผ่าน Port : 32010

Service type

จากส่วนที่ผ่านมาเราจะเห็นว่าเรามีความต้องการใช้ Service ในหลายความต้องการ เช่น เรียกด้วยชื่อ service , เรียกจาก client ที่อยู่นอก cluster ดังนั้นเพื่อตอบสนองความต้องการที่หลากหลายจึงมีการกำหนด Service type เพื่อให้ระบุว่า Service นี้ต้องทำงานแบบไหน

จริงๆมีคนเขียนเกี่ยวกับ Service type ไว้ดีมากพร้อมมีภาพที่ทำให้เข้าใจง่าย ซึ่งผมเชื่อว่าดีกว่าผมอธิบาย คุณสามารถอ่านได้ที่ : https://thanwa.medium.com/kubernetes-%E0%B8%A7%E0%B9%88%E0%B8%B2%E0%B8%94%E0%B9%89%E0%B8%A7%E0%B8%A2%E0%B9%80%E0%B8%A3%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B8%87-services-%E0%B9%81%E0%B8%95%E0%B9%88%E0%B8%A5%E0%B8%B0%E0%B8%9B%E0%B8%A3%E0%B8%B0%E0%B9%80%E0%B8%A0%E0%B8%97-25bade6d4725

โดยในบทความนี้จะพูดถึงแค่ 2 Service type คือ ClusterIP กับ NodePort เท่านั้น ส่วน Service type อื่นๆผมไม่ขอพูดถึงเพราะว่า เราไม่สามารถทดลองใช้งานได้ง่ายๆและน่าจะยังไม่มีตัวอย่างที่เหมาะสมในการแสดงให้ดู

ClusterIP

Service Type ประเภทนี้จะอนุญาตให้เข้าถึงได้เฉพาะสิ่งที่อยู่ใน Cluster ของ k8s เท่านั้น จะเห็นว่าตอนเราสร้าง Service Type ด้วย ClusterIP เราจะต้องใช้ client-pod ที่อยู่ใน cluster ของ k8s ในการทดลองยิง request หากเราลองเปิด browser ของเครื่องเรา แล้วใส่ IP ของ Service แล้วยิงไปจะเห็นว่าไม่สามารถยิงไปหาได้

NodePort

Service Type ประเภทนี้จะอนุญาตให้เครื่องที่อยู่ภายนอกสามารถเชื่อมต่อกับ Pod (ผ่าน Service) ผ่าน IP ของเครื่องที่ Run k8s และ Port จากในตัวอย่างที่เราจะเห็นว่าเราสามารถใช้งาน nginx-pod ผ่าน browser โดยยิงไปที่ url : http://<ip เครื่องที่ run k8s>:32010

  • ข้อควรระวัง : ตัว NodePort มีข้อจำกัดที่จะใช้ Port ได้แค่ Port : 30000-32767 เท่านั้น

สรุป

สำหรับตอนนี้เราได้เรียนรู้เกี่ยวกับ Service รู้ที่มาที่ไปของมัน วิธีการสร้าง Service ผ่าน Configuration file ความแตกต่างระหว่าง Service type : ClusterIP กับ NodePort ส่วนในตอนต่อไปเราจะมีเรียนรู้เกี่ยวกับ Volume กัน

Basic Kubernetes Part 2 - Pod คืออะไรและวิธีการใช้งานผ่าน Deployment

Basic Kubernetes Part 2 - Pod คืออะไรและวิธีการใช้งานผ่าน Deployment

Pod คืออะไร

ถ้ามองง่ายๆแบบไม่อิงนิยามที่ถูกต้องแบบเป๊ะๆ Pod ก็คือเครื่อง 1 เครื่อง โดยที่เครื่องเครื่องนั้นลง Docker และสามารถ Run container ได้ (บอกไว้ตรงนี้เลยว่านิยามนี้ไม่ถูก แต่อยากอธิบายเพื่อให้เข้าใจง่าย นิยามที่ถูกต้องดูได้จาก : https://kubernetes.io/docs/concepts/workloads/pods/) และในเมื่อมันเป็นเครื่อง ดังนั้นมันจึงมี Volume ซึ่งเราสามารถ Mount volume เข้าไปที่เครื่อง แล้วให้ทุก Container สามารถเข้าถึง Volume นั้นได้ ภาพของ Pod จะเป็นดังตัวอย่างด้านล่าง

ภาพจาก : https://kubernetes.io/docs/tutorials/kubernetes-basics/explore/explore-intro/

จากภาพจะเห็นว่า Pod นั้น Run หลาย Contianer และใน 1 Pod มีหลาย Volume อยู่ และ 1 Pod นั้นจะถูกกำหนด IP ให้สามารถเรียกเข้าไปใช้งานได้

สำหรับ k8s นั้นตัว Pod จะถือเป็นหน่วยที่ถูก Deploy ซึ่งอาจจะถูก Deploy ด้วย Deployment , DaemonSet , StatefulSets ซี่งเดี๋ยวเราค่อยมาทำความเข้าใจกันทีหลัง รู้แค่ว่าตัว Pod จะต้องถูก Deploy

สร้าง Pod ยังไง

ในการสร้าง Pod นั้นสามารถสั่งได้ด้วยคำสั่ง kubectl โดยสามารถสร้างได้ด้วย cmd ตรงๆเลย หรือจะสร้างผ่าน File ที่ทำการบอกว่า pod นั้นมีต้องมีอะไรบ้าง (ไฟล์ที่เราทดลองทำกันใน Part 1) โดยที่เราจะเรียนรู้กันนี่จะเป็นแบบสร้างแบบ File (เขามีภาษาทางการว่า Declarative Configuration Files) เพราะในงานจริงที่เราทำกันนั้น เราไม่สั่ง cmd เป็นคำสั่งๆเพื่อให้ตัว k8s ทำงานให้เพราะมันยากที่จะตรวจสอบว่าสั่งอะไรไปแล้วบ้าง และส่วนใหญ่เราก็ลืมว่าสั่งอะไรไป แต่ถ้าเราทำเป็น Configuration Files นั้นเราจะรู้ว่าเราสั่งสร้างมันยังไง อีกทั้งเมื่อทำเป็น File เราสามารถ Save ลง Git เพื่อเอาไปใช้งานได้อย่างหลากหลายเพิ่มขึ้น ไม่ว่าจะเอาไปทำ CD (Continuous deployment) ทำการตรวจสอบหาการเปลี่ยนแปลงที่ทำให้เกิดปัญหา เป็นต้น

เริ่มสร้าง Pod พื้นฐาน

เนื่องจากต้องเขียน Configuration Files เป็นจำนวนมาก ผมเลยทำ Git ไว้เก็บตัวไฟล์ เผื่อใครขี้เกียจ Copy หรือ พิมพ์ (ผมแนะนำให้ลองพิมพ์เองดูสักหน่อยเพื่อให้เกิดความคุ้นเคยว่ามันต้องมี keyword อะไรบ้างในไฟล์) สามารถไป Clone Repository ได้ที่ Git ตามลิ้งนี้ https://github.com/surapong-taotiamton/training-k8s

สร้างไฟล์ : pod-1.yml จากนั้นใส่เนื้อไฟล์ข้างล่าง

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80

จากนั้นสั่งคำสั่งด้านล่างเพื่อทำการสร้าง Pod

1
kubectl apply -f pod-1.yml

จากนั้นสั่งคำสั่งด้านล่างเพื่อทำการดูข้อมูล Pod ที่เราสร้างขึ้นมา

1
kubectl get pods

จากภาพจะเห็นว่าเราสามารถสร้าง Pod ขึ้นมาได้แล้ว

คราวนี้เราลองมาดูรายละเอียดของ Pod กัน โดยเราจะสั่งโดยใช้คำสั่ง (คำสั่งเหมือนเยอะ แต่ที่ใช้บ่อยๆมันมีแค่ get , apply ,delete , describe logs ซึ่งพอใช้ไปจะรู้เองว่า ถ้าอยากดูให้คำสั่งนี้ อยากสร้าง อยากลบ ใช้คำสั่งนี้ )

1
kubectl describe pod nginx-pod

ขอให้ดูตรงที่ผมครอบแดงไว้ อย่างแรกคือ IP Pods ตัวนี้จะถูก Assign IP ให้เป็น 10.42.0.59 ซึ่ง IP นี้ตัว k8s จะเป็นคนกำหนดให้ ดังนั้นเครื่องคุณกับเครื่องผมจะไม่เหมือนกัน จากนั้นให้คุณลองสั่ง

1
2
3
4
curl <ip ของ pod>
# ของผมคือ 10.42.0.59 เลยได้เป็น

curl 10.42.0.59

ซึ่งจะเห็นว่าสามารถยิง http request ไปหา Pod ที่เราสร้างได้

ส่วนต่อมาที่อยากให้ดูคือ Image เราจะเห็นว่า Image ของ Pod นี้คือ nginx:1.25.1 ซึ่งจะตรงกับที่เรากำหนดไว้ในไฟล์ Configuration

จากทั้งหมดจะเห็นว่าเราสามารถสร้าง Pod ขึ้นมาได้ผ่านไฟล์ pod-1.yml

อธิบายไฟล์ pod-1.yml

ในไฟล์ pod-1.yml จะประกอบด้วย keyword ที่ใช้กำหนด pod ว่ามีต้องมีอะไรบ้าง โดยเราจะค่อยๆอธิบายทีละ keyword เพื่อให้เกิดความเข้าใจ

apiVersion

keyword นี้เป็นตัวบอกว่าตัวการประกาศนี้ต่อจากนี้นั้นใช้ version อะไร โดยที่เขาทำแบบนี้เนี่ยเพราะตัว k8s นั้นต้องรองรับการ deploy ผ่านหลาย version ไม่ใช่พอ Update k8s เป็น version ใหม่ปุ๊ปจะไม่ Support ตัวการประกาศ version เก่าแล้ว ดังนั้นเลยต้องมี apiVersion เพื่อบอกให้ k8s ทราบ

kind

เป็นตัวบอกว่าสิ่งที่สั่งให้ k8s สร้างคืออะไร ซึ่งในไฟล์นี้คือ Pod ก็แปลว่าสั่งให้สร้าง pod

metadata

ส่วนนี้จะเป็นการบอกค่าคุณลักษณะของ pod ตัวนี้ เช่น กำหนดชื่อ กำหนดว่ามันจัดอยู่ในกลุ่มอะไร โดยคุณลักษณะพวกนี้ไม่ได้เกี่ยวกับว่าใช้ Image อะไร ใช้ Ram เท่าไหร่ แต่เป็นคุณลักษณะที่เราอยากเพิ่มเข้าไปเพื่อใช้ในจุดประสงค์เรื่องการจัดการ หรือ ทำอย่างอื่น โดยคุณลักษณะที่เรา set ในตอนนี้คือ

1. name

อันนี้เป็นการบอกว่า pod นี้จะให้เรียกชื่อว่าอะไร

2. labels

ส่วนนี้เป็นเหมือนการแปะ label (ฉลาก) ให้กับ pod นี้ว่ามีค่าอะไรบ้าง ถ้านึกภาพไม่ออกให้นึกว่าคุณอยู่โกดังคัดแยกสินค้า คุณเป็นเจ้าหน้าที่ตรวจสอบสินค้าเพื่อบอกคุณลักษณะของสินคาว่ามีคุณลักษณะอะไร เช่น บอบบาง เป็นอาหาร มีประกัน ส่งด่วน เพื่อให้คนที่รับงานต่อจากคุณสามารถดำเนินการต่อได้ถูก คุณจะทำยังไงให้ง่าย วิธีง่ายๆที่คิดคือคุณสร้าง ฉลากขึ้นมาและแปะไปกับตัวสินค้าเพื่อให้คนที่รับหน้าที่ไปดำเนินการต่อรู้ว่ามันมีคุณลักษณะอะไร เช่น ถ้าส่งด่วน คุณอาจแปะฉลากสีแดงเขียนว่าด่วนไปกับสินค้า คนที่มีหน้าจัดการต่อพอเห็นฉลากนั้นก็จะทราบทันทีว่าส่งด่วน หรือ ถ้าแปะฉลากสีน้ำเงินว่ามีประกัน คนที่รับหน้าที่ดูแลต่อจะทราบว่ามีประกัน ต้องไปทำเอกสารเกี่ยวกับประกัน โดยตัวฉลากนั้นไม่จำเป็นต้องมีอันเดียว เราสามารถแปะได้หลากหลาย เช่น แปะฉลากสีแดง และ สีน้ำเงิน ทำให้รู้ว่า ส่งด่วนและมีประกัน

จากตัวอย่างเราแปะ label ชื่อ app แล้วมีค่าว่า nginx ให้กับ pod นี้

spec

ส่วนนี้จะเป็นการบอกให้ k8s ทราบว่า pod ที่เราสร้างเนี่ยจะใช้ container อะไรบ้าง ใช้ Image อะไร เปิด port อะไร

1. containers

ตรงนี้เป็นการบอกว่า container ใน pod มีอะไรบ้าง (จะเห็นว่าเขาเติม s) จากตัวอย่าง

1
2
3
4
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80

ตรงนี้จะเป็นการบอกว่า container ใน pod ชื่อ nginx-container ใช้ Image nginx:1.25.1 และเปิด port : 80

สร้าง Pod แบบมีหลาย Container

สร้างไฟล์ : pod-2-multiple-container.yml โดยเราจะเห็นว่า pod นี้ต่างจาก pod ที่แล้วคือ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: Pod
metadata:
name: pod-multiple-container
labels:
app: test-multiple-conatainer
site: test
spec:
containers:
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 81
env:
- name: WHOAMI_PORT_NUMBER
value: "81"
  1. เรากำหนด label มากกว่า 1 ตัวคือมี label
1
2
app: test-multiple-conatainer
site: test
  1. มีการสร้าง Container มากกว่า 1 ตัวให้ Pod ตัวนี้คือ nginx-container , whoami-container

  2. ตัว whoami-container มีการกำหนด Environment โดยกำหนด Environment ชื่อ WHOAMI_PORT_NUMBER ให้กับ whoami-container โดยสั่งผ่าน

1
2
3
env:
- name: WHOAMI_PORT_NUMBER
value: "81"

จากนั้นสั่ง

1
2
kubectl apply -f pod-2-multiple-container.yml
kubectl describe pod pod-2-multiple-container.yml

ลอง curl ไปเช็ค pod ที่คุณสร้างขึ้นมาใหม่ (อย่าลืม IP ของเราไม่เหมือนกันนะครับ) โดยยิงไปที่ port 80 เพื่อดูว่า nginx ทำงานไหม ยิงไปที่ port 81 เพื่อดูว่า whoami ทำงานไหม

1
2
3
4
# ของผม ip ของ pod คือ 10.42.0.62

curl 10.42.0.62:80
curl 10.42.0.62:81

ซึ่งจากภาพเราจะเห็นว่าทั้ง 2 container บน pod เดียวกันสามารถทำงานได้

อยากสร้าง Pod หลายๆตัวที่หน้าตาเหมือนกันทำยังไง

ตอนนี้เราสามารถ pod : nginx เพื่อรองรับ request ได้แล้ว ทุกอย่างดูเหมือนเรียบร้อยแต่ทุกปัญหาก็เกิดขึ้นเมื่อ pod ที่คุณสร้างนี่รับ request ที่มาจากลูกค้าไม่ไหว วิธีที่เราจะจัดการปัญหานี้ในเบื้องต้นคือการ scale out หรือถ้าเทียบกับความเป็นจริงคือเพิ่มสิ่งที่เหมือนกันเข้าไปเพื่อช่วยทำงาน เช่น ถ้าคุณไปซื้อของในห้างสรรพสินค้าแล้วในห้างมีคนรอคิวจ่ายเงินเยอะมากๆ สิ่งที่ห้างจะแก้ปัญหาคนที่รอคิวนานๆได้คือ เปิดช่องจ่ายเงินเพิ่มอีก 1 ช่อง เพื่อให้คนไปจ่ายเงินช่องใหม่ เวลาในการรอคิวก็ลดลง

ด้วยความรู้ที่คุณมีอยู่ตอนนี้คืออยาก scale out ให้มี nginx 3 ตัวคุณจะทำยังไง วิธีการไม่ยากครับคุณสามารถทำได้โดยทำแบบนี้คือ Copy วางแล้วเปลี่ยนชื่อ pod เอาดังด้านล่าง

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
apiVersion: v1
kind: Pod
metadata:
name: scale-nginx-pod-1
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
name: scale-nginx-pod-2
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
name: scale-nginx-pod-3
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80
---

ทุกอย่างดูเหมือน ok ครับ แต่ปัญหาคือถ้าผมอยากได้ 5 ตัวล่ะ แล้วอยู่ดีๆผมอยากลดให้มันเหลือ 2 ตัว หรือ 1 ตัวดูล่ะ เรื่องแบบนี้เกิดขึ้นได้ครับ แบบในห้างสรรพสินค้าที่ในช่วงคนเยอะช่องจ่ายเงินอาจเปิดหลายช่อง แต่ช่วงเวลาคนน้อยเขาเปิดแค่ 1 ช่องเท่านั้น ถามว่าวิธีทำมีไหม มีครับ ถ้าอยากลบเราก็แค่สั่ง

1
2
kubectl delete scale-nginx-pod-3
kubectl delete scale-nginx-pod-2

ถ้าสั่งเพิ่มก็ไปสั่งสร้าง pod เพิ่มในไฟล์เอา แต่มันใช่วิธีที่สะดวกไหม ซึ่งก็ขอบอกเลยว่าไม่สะดวก และยุ่งยาก ดังนั้นเพื่อตอบโจทย์การเพิ่มลดตัว pod แบบง่ายๆ จึงมีสิ่งที่เรียกว่า Deployment เกิดขึ้นมาเพื่อช่วยเราแก้ปัญหานี้

Deployment

Deployment คือตัวที่ทำหน้าที่บริหารจัดการสร้าง pod ให้ได้ในลักษณะที่ต้องการ เช่น มีจำนวน pod เท่าไหร่ (นิยามของผมไม่ถูกต้อง 100% นิยามแบบถูกต้อง 100% ดูที่ https://kubernetes.io/docs/concepts/workloads/controllers/deployment/ )

มาลองสร้าง Deployment กันโดยทำการสร้างไฟล์ : deployment-1-basic.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 5
selector:
matchLabels:
app: nginx
template:
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80

จากนั้นสั่งคำสั่งด้านล่างเพื่อสร้าง deployment

1
kubectl apply -f deployment-1-basic.yml

ลองสั่ง get และ describe deployment เพื่อดูว่าเราสามารถสร้างได้สำเร็จหรือไม่และ deployment มีรายละเอียดอะไร

1
2
kubectl get deployment
kubectl describe deployment nginx-deployment

จากภาพจะเห็นว่าเมื่อสั่ง get มี deployment ชื่อ nginx-deployment และมี pod ที่ต้องสร้างภายใต้ deployment นี้คือ 5 ตัว และเมื่อดูรายละเอียดด้วย describe จะพบรายละเอียดว่า deployment นี่สร้าง pod แบบไหน มีจำนวนที่ต้องสร้างเท่าไหร่ และอื่นๆ (จริงๆมีรายละเอียดการสร้างว่าจะค่อยๆ update แบบไหน ค่อยๆสร้างแล้วเปลี่ยน หรือ สร้างหมดแล้วค่อยเปลี่ยน หรือแบบไหน แต่ตอนนี้เรายังไม่ลงรายละเอียด)

คราวนี้เราลองสั่ง

1
kubectl get pods

จะเห็นว่ามี pod ที่ชื่อขึ้นต้นด้วย nginx-deployment โผล่ขึ้นมา 5 ตัว ซึ่ง pod เหล่านี้คือ pod ที่ถูกสร้างขึ้นมาจาก deployment

อธิบายไฟล์ deployment-1-basic.yml

ในส่วนของ apiVersion , kind, metadata ไม่ขออธิบายนะครับเพราะเหมือนกับ pod เราจะมาอธิบายส่วนอื่นๆที่ไ

spec

ตรงนี้คือการบอกว่าเราอยากได้ Deployment แบบไหน

1. replicas

ตรงนี้เป็นการบอกว่าจำนวนสร้าง pod จำนวนกี่ตัว ตรงนี้กำหนดไว้ 5 คือต้องการมี pod 5 ตัว

2. matchLabels

ตรงนี้เป็นการบอกว่าจะจัดการ pod นั้นโดยการ search หาด้วย label อะไร ซึ่งในที่นี้คือ app: nginx ซึ่งค่านี้จะต้องสัมพันธ์กันกับค่า label ในส่วนของ template

3. template

ตรงส่วนนี้เป็นการบอกว่า pod ของเรามีหน้าตาที่จะให้สร้างอย่างไร หากสังเกตดีๆค่าที่อยู่ใน template นั้นคือการ copy ค่าจากส่วน metadata จนถึง spec ใน pod มาใส่ ไม่เชื่อลองไปดูไฟล์ pod-1.yml

1
2
3
4
5
6
7
8
9
10
11
# ส่วน metadata ไปจนถึง spec
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80

ลองลบ Pod เล่นดูว่าจะเกิดอะไรขึ้น

เรามาลองลบ pod ที่เกิดจากการที่ Deployment สร้างดูว่าจะเกิดอะไรขึ้น

1
2
# kubectl delete pod [ชื่อ pod] ที่ได้มาจากการ get
kubectl delete pod nginx-deployment-59ccdc84fc-rtq7b

จากภาพเราจะเห็นว่าเมื่อเราลบ pod ทิ้งไป แต่เมื่อเราสั่ง get pods มาดูใหม่จะเห็นว่า pod ที่เราลบหายไป (ตัวสีแดง) แต่จะมีตัวใหม่เพิ่มเข้ามา (ตัวสีเขียว) ดังนั้นจะเห็นว่าไม่ว่า pod ของเราจะหายไปด้วยเหตุผลอะไร ตัว deployment จะพยายามสร้างตัว pod ขึ้นมาให้ได้เท่าเดิม

ลองเพิ่มลดจำนวน replicas ดูว่ามีอะไรเปลี่ยนแปลง

ลองเปลี่ยนค่า replicas เป็น 10 จากนั้นสั่ง apply ดูแล้วดูว่าเกิดอะไรขึ้น

จากภาพจะเห็นว่า replicas เพิ่มขึ้นเป็น 10

ลองเปลี่ยน replicas เป็น 1 แล้วลอง apply ดู ซึ่งเมื่อสั่งแล้วจะเห็นว่าตัว pod นั้นเหลือแค่ 1 ตัวแล้ว

สรุป

สำหรับตอนนี้เราได้เรียนรู้ว่า Pod ใน k8s นั้นคืออะไร มีวิธีสร้างผ่าน file ยังไง ลองสร้าง pod ในรูปแบบที่มีหลาย container เรียนรู้ keyword ต่างๆของ pod (ซึ่งบอกเลยว่ายังเหลืออีกหลายอันที่ต้องไปศึกษาเพิ่ม) ไปจนเห็นปัญหาการในการใช้งานจริงว่าถ้าต้อง scale out แล้วมีแค่ pod อย่างเดียวจะเกิดปัญหาอะไร ซึ่งทำให้เราไปใช้ Deployment เพื่อแก้ปัญหานี้

หวังว่าตอนนี้จะทำให้คนที่อ่านได้เข้าใจเกี่ยวกับ Pod และ Deployment ในตอนต่อไปเราจะมาใช้งานตัว Service กัน