สรุป Helm essential course — Day4 (end)
chapter นี้เป็น chapter สุดท้ายแล้ว จะมีเนื้อหาดังนี้:
- Validate / verify Helm chart
- Writing Helm chart
- Helm Chart hook
เนื้อหาใน chapter นี้จะใช้องค์ความรู้จาก chapter ก่อนหน้าทั้งหมด ซึ่งผู้อ่านสามารถเข้าอ่านเนื้อหาของ chapter ก่อนหน้าได้ดังนี้
Validate / Verify Helm chart
Helm มี 3 command ที่จะช่วยตรวจสอบ syntax และ validate การ replace ค่า predefine ไปยัง k8s manifest file
- Helm lint
Lint จะช่วยเราในการตรวจสอบว่า format ของ yaml ใน helm template มีการใช้งาน syntax ที่ถูกต้อง
$ helm lint <chart_path>
//ถ้าไม่ระบุ chart_path -> ค่า default จะเป็น . หรือนั่นคือ path ที่อยู่ปัจจุบันของ terminal
เช่น
จากตัวอย่างที่ไฟล์ deployment.yaml ผมลอง scenario นี้คือ [1]เพิ่ม replicas ไปใน metadata.label ซึ่งจริงๆใน metadata.label จะไม่มี attribute replicas อยู่ และ [2]ปรับให้ spec.replicas: มีค่าเป็น {{ .Valuaes.xx}} คือ case ที่เราเขียนผิด ซึ่งเมื่อใช้ helm lint พบว่า…
ตัว lint จะบอกแค่ว่าผิดพลาดขึ้นที่บรรทัด 11 ที่ตัวแปล .Valuaes ซึ่งก็ถูกต้อง แต่เหมือนแก้ไขให้เขียนเป็นคำว่า Values ที่ถูกต้องแล้ว ซึ่ง lint ก็ปล่อยผ่านเลย ทั้งๆที่จะมีผิดอีก 1 จุดคือ ที่ metadata.labels.replicas
ซึ่งถ้าเราลอง install ตัว chart ลงบน local จะเห็นว่าถึง lint จะปล่อยผ่าน แต่ถ้า install ตัว helm chart จริงมันจะไม่สามารถนำ k8s manifest ไปสร้างเป็น k8s object / resource ได้ เนื่องจากมันผิดที่ k8s นั่นเอง
💡 จะเห็นได้ว่าการใช้ lint จะใช้แค่การตรวจสอบ syntax ของ yaml เท่านั้น ซึ่งไม่ได้การันตีว่าตัว k8s manifest จะ apply ผ่าน บน k8s นะ
2. Helm template
Template จะช่วยตรวจสอบในเรื่องของการ replace ค่า redifined value จาก value file, chart, capacity สามารถดู replace ลงได้ใน k8s manifest
$ helm template <chart_path>
//ถ้าไม่ระบุ chart_path -> ค่า default จะเป็น . หรือนั่นคือ path ที่อยู่ปัจจุบันของ terminal
เช่น
ที่ไฟล์ deployment มีการใช้ predefined ค่าจาก helm value
$ helm template astronut-chart .
ซึ่งถ้าหากเราใช้ค่า predefined ที่ไม่มีอยู่ มันก็จะเป็น nil / null ซึ่งก็สามารถใช้ข้อมูลส่วนนี้ debug ได้ว่าการใช้ค่า predefined ของเรานั้นถูกต้อง
การใช้ template ก็จะเข้ามาช่วย debug การ replace ค่า predefined ประมาณนี้
3. Helm dry run
Dry run จะเป็น option ที่จะเจอใช้บ่อยๆ ใน helm install หรือ helm upgrade ซึ่งมันจะยิงไปหา k8s api server แล้วบอกว่าให้ k8s interact กับ helm ด้วย command หลัก แต่จะให้อยู่ในสถานะ pending (ซึ่งก็คืออยากดูผลลัพธ์ แต่ไม่ต้องทำการสร้าง หรือ upgrade) ซึ่งด้วยคุณสมบัตินี้ ตัว dry run จะช่วยให้เราสามารถ debug และ มั่นใจได้ว่า chart ที่เราเตรียมขึ้นมานั่น มันสามารถ apply และ สร้าง k8s object ขึ้นมาได้จริงๆ
$ helm install <release_name> <chart_repo> --dry-run
$ helm upgrade <relase_name> --version=??? --dry-run
//--dry-run โดย default แล้วจะมีค่าเท่ากับ --dry-run=client
เช่น
$ helm install astronut-app . --dry-run
จะเห็นว่า output จะมีสถานะเป็น pending-install และ list ออกมาว่า manifest ที่ validate ผ่าน k8s และพร้อมที่จะสร้าง k8s object
มาเช็คกันให้เห็นเลยว่า dry run จะยังไม่ได้สร้าง k8s object ขึ้นมานะ…
$ kubectl get deployment
$ kubectl get pod
📍Checkpoint1: ในหัวข้อ Validate / Verify Helm chart นี้ การใช้ lint, template และ dry run จะทำให้เรา validate, debug และมั่นใจได้ว่า ตัว helm template ที่เราได้สร้างขึ้นมาสามารถนำไปใช้งานได้จริง
- Lint: ใช้ตรวจสอบ syntax ของ yaml ของ k8s manifest file ใน helm template
- Template: ใช้ตรวจสอบการ replace ของค่า predefined จาก helm value / chart / capacity
- Dry run ใช้ช่วยในการจำลองการ install หรือ upgrade ตัว helm chart เพื่อให้มั่นใจได้ว่า k8s manifest ของเรานั่นถูกหลักของ k8s และสามารถ apply ลง k8s cluster ได้
Writing helm chart
ในการเขียน chart หลักๆ เราก็จะเน้นไปที่การเขียน k8s manifest file ที่อยู่ใน helm template และจะมีการใช้ร่วมกับการทำ predefiend ซึ่งตัว helm ก็จะมีฟังก์ชันมาช่วยเราให้การเขียน config ของ k8s manifest ให้มีความง่ายต่อการเขียน, อ่าน, ปรับปรุง และการนำไป reuse ใช้
- Declare variable
เราสามารประกาศตัวแปร เพื่อใช้เก็บค่าใน {{ }} ได้ด้วย ซึ่งจะเป็น syntax เดียวกันกับ Go (เนื่องจาก helm ถูกสร้างด้วย Go)
{{ $name := .Value.astronut.app.name }}
เช่น เราประกาศตัวแปล appname ไว้ที่บรรทัดที่ 2 ซึ่งจะไปอ่าน predefined จาก helm value และไปใช้ตัวแปล appname บรรทัดที่ 10
2.Function และ pipeline
ในตัว {{ }} ที่เราใช้ในการกำหนดค่า predefine สามารถนำ function เข้ามาช่วยจัดการได้ เช่น
//upper function -> ทำให้เป็นตัวอักษรใหญ่ทัั้งหมด
{{ upper .Release.Name }}
//default function -> ถ้าไม่มีการะบุ predefined จะกำหนดค่านั้นด้วยค่า default
{{ default "spaceworld" .Release.Name }}
และการนำ pipeline ( | ) มาใช้, ตัว pipe จะใช้ได้เหมือนกับของ linux เลย คือ นำส่วนหน้ามา process ด้วยฟังก์ชันหลัง |
{{ default "spaceworld" .Value.astronut.app.name | upper | quote }}
*ตัว | quote จะเป็นการใส่ “ ” ให้กับ value นั้น
3. Condition
condition หรือเงื่อนไข if — else ที่เราคุ้นเคยกันดี ซึ่งก็มี logic เดียวกัน
{{-if <helm-value.key> }}
...
{{-end }}
*Tip - คือการ trim บรรทัดนะ
เช่น ที่ helm value ส่วนของ readiness และ liveness มี sub key เป็น enabled ซึ่งตั้งให้ readiness เป็น true และ liveness เป็น false … เมื่อลอง helm template ออกมาจะบนว่าจะมีแค่ค่า config ของ readiness (สีแดง) เนื่องจากเช็ค condition ด้วย true แต่ตัว liveness (สีเขียว) จะไม่ถูก cofig เนื่องจากเงื่อนไข condition เป็น false
4. Loop / Range
การวน loop เพื่อเอาเค้ามา config ก็ลักษณะเดียวกันกับ loop ของการ coding
{{- Range $key, $value := $<variable>}}
...
{{- end }}
เช่น เรามี helm value หน้าตาประมาณนี้คือ ใต้ dep จะมี deploymeny มากกว่า 1 ตัว และต้องการจะ loop เพื่อ config readiness และ liveness ของแต่ละ deployment
{{ $app := .Values.astronut}}
{{- Range $key, $value := $app.readiness}}
{{- end}}
5. With block
with block คือการทำจุดที่ร่วม เพื่อจะใช้ key ภายใต้ร่วมกัน
{{- with .Value.astronut }}
{{- end }}
เช่น ใช้ with block ที่ level readiness และ liveness จากนั้นก็ให้ใช้ attribute ภายใต้ด้วย .<key_name> ได้เลย
6. Name template / Partial
เป็นการแยกส่วนขอ value ที่เราต้อง reuse ใช้ในหลายๆที่ ออกมาเป็นไฟล์ _<file_name>.tpl จากนั้นก็เรียกใช้ด้วย .include
//create nametemplate file
eg. _helper.tpl
{{- define "<name_template>" }}
...
...
{{- end }}
//using nametemplate by include
{{- include "<name_template>"}}
เช่น เราสร้างไฟล์ _helpers.tpl ขึ้นมาเพื่อระบบ name template ชื่อ application ผ่านฟังก์ชัน define … จากนั้นเรียกใช้ผ่านฟังก์ชัน include ตามด้วยชื่อของ name template ซึ่งเติม pipe ด้วย ฟังก์ชัน nindenet 8 คือให้จัด indent = 8 ก็จะได้ผลลัพธ์ที่ต้องการดัง terminal ด้านล่าง
จิงๆ แล้วในการเรียกใช้ name template สามารถเรียกผ่านฟังก์ชัน template นะได้ แต่ท่านี้จะมีข้อจำกัดอยู่ 2 ข้อหลักๆ 1.)ค่าที่เรา define อยู่ใน _xx.tpl จะต้องเป็นค่าคงที่ ถ้าเป็นค่า predefine มันจะหาไม่เจอ เนื่องจากมันไม่รู้ scope 2.) จะไม่สามารถอ่าน indent ที่กำหนดไว้ใน name template ได้ … ดังนั้นเราก็เปลี่ยนไปใช้ include ที่มีความยืนหยุ่นสูงกว่า จึงตอบโจทย์ได้มากกว่า
📍checkpoint2: ในหัวข้อนี้เราจะใช้ความสามารถของภาษา Go มาช่วยให้เราสามารถเขียน k8s manifest file เพื่อให้สามารถมใช้งานร่วมกับ helm value ได้ง่ายขึ้นผ่านการใช้ 6 อย่างที่ helm มีให้เราเลือกใช้
Helm Chart hook
ในส่วนของการทำงานเมื่อเราสั่ง helm install หรือ helm upgrade นั่น มันจะมีการวิ่งไปที่ k8s เพื่อทำการ validate เจ้า k8s manifest และ apply manifest เพื่อมาสร้างเป็น k8s object บน k8s ในขั้นสุดท้าย ซึ่งจริงๆ แล้วในส่วนก่อนจะเกิดการสร้าง k8s object และ หลังการสร้าง เราสามารถ handle ตัว event มันได้ เพื่อที่เราจะสามารถนนำไปประยุกต์ใช้เพิ่มเติม เช่น เคสก่อน (pre) หรือหลัง (post) ที่จะสร้าง k8s object ให้ทำการส่ง email หรือ slack บนทีมให้ทราบ และอีกเคส คือถ้าหาก helm install หรือ helm upgrade fail ต้องการทำ rollout plan เช่นหาก upgrade ผิดพลาดจะต้อง handle ด้วยการทำ batch 1,2,3,4 เป็นต้น
เจ้าตัว hook เนี่ย เรากำหนด annotation ให้ได้ด้วยนะ
- hook-succeeded: ให้ลบ resource (k8s job) หลังจาก hook ได้ทำงานเสร็จสิ้น
- hook-failed: ให้ลบ resource ถ้า hook เกิด fail ระหว่างทำงาน
Activity time — play with pre and post chart hook!
โจทย์คือเมื่อเรา install ตัว helm-chart อยากให้ alert message เข้า slack
- pre hook เราจะให้ส่ง message “Helm chart installation is starting…”
- post hook เราจะให้ส่ง message “Helm chart installation has Done!!!”
โอเครมาลุยกัน
เพิ่ม job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: send-pre-install-message
annotations:
"helm.sh/hook": pre-install
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
containers:
- name: slack-notifier
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
curl -X POST -H 'Content-type: application/json' --data '{"text":"Helm chart installation is starting..."}' 'icon_emoji: :helm:' <slack_webhook_url>
restartPolicy: Never
---
apiVersion: batch/v1
kind: Job
metadata:
name: send-post-install-message
annotations:
"helm.sh/hook": post-install
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
containers:
- name: slack-notifier
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
curl -X POST -H 'Content-type: application/json' --data '{"text":"Helm chart installation has Done!!!"}' 'icon_emoji: :helm:' <slack_webhook_url>
restartPolicy: Never
ในตอนนี้ chart template เราจะมี deployment.yaml และ job.yaml นะ (เพิ่มทั้ง pre hook และ post hook ไปในไฟล์เดียวเลยละกัน) เมื่อพร้อมแล้วเรามา install กัน
$ helm install <relase_name> .
ตอนที่ยิง helm install ตัว cursor ของ terminal จะค้างอยู่ นั่นคือมันรอให้ตัว job ทำงาน โดยยิงไปที่ slack ที่ได้ set up ไว้ให้เสร็จก่อน โดย pre hook และ post hook จะทำงานในส่วนนี้ และเมื่อเสร็จก็จะได้ result คืนมาที่ cursor. เป็นอันเสร็จสิ้น เย้ 🥳
📍checkpoint3: ในหัวข้อนี้ เราได้เรียนรู้ว่าในโฟล์หลังบ้าน เมื่อเราสั่ง helm install หรือ helm upgrade ใน process ก่อนและหลังที่จะมีการสร้าง k8s object นั้น จะมี 2 ตัวที่คั้นอยู่ส่วนหน้าและหลัง นั่นคือ pre hook และ post hook ซึ่งเราสามารถนำทั้ง 2 ตัวนี้ไปประยุกต์ใช้เพื่อให้เราทำงานได้ง่ายขึ้นได้นะ
บทสรุป
ขอขอบคุณที่ได้ตามอ่านกันมาถึงบทสุดท้ายนี้ ซึ่งเราได้เรียนรู้อะไรมากมายเกี่ยวกับเจ้าตัว Helm เลยนะ ไม่ว่าจะเป็นบทที่หนึ่ง ที่เราเพิ่งจะรู้จัก Helm -> ประวัติความเป็นมาของการเกิด Helm, Helm componenet. ในบทที่สอง กับเนื้อหาที่เข้มข้นขึ้นที่เราได้เล่นและเรียนรู้กับ Helm lifecycle managment, Helm Environment, Helm chart Repository. ในบทสามที่เราได้สร้าง Helm chart เป็นของตัวเราเอง การ predefiend ค่าจาก value.yaml และ chart.yaml, การ pack helm chart และการ push ตัว helm chart ขึ้นไปเก็บไว้ที chart repository และการเรียกใช้งานจาก chart repository เหมือนกับ chart เจ้าอื่นๆ. สุดท้ายนี้ด้วยการ validate / verify ตัว helm chart เพื่อให้เรามั่นใจว่า helm chart ของเราถูกต้องทั้ง syntax, ทั้งการ replace ค่าจาก predefined และสามารถ apply ลง k8s cluster ได้, การเขียน helm chart ด้วยพลังของ Go และ helm ที่จะทำให้การเขียน replace ค่า helm value (predefine) ไป k8s manifest ได้สะดวก และง่ายยิ่งขึ้น ปิดจบด้วยการเล่นกับ chart hook เพื่อให้เราจัดการทำงานได้ก่อนและหลังการ install, upgrade, rollback และ uninstall ตัว helm chart ได้
… ก็ประมาณนี้ครับ ไว้เจอกันบทความหัวข้อการเรียนรู้อื่นๆต่อไป ขอบคุณครับ 🙏🏻