สรุป Helm essential course — Day3
chapter นี้จะเน้นไปที่การ create, pack และ push helm chart ของเราไปยัง chart repository.
โดยหัวข้อหลักๆ มีดังนี้
1. Helm Package
2. Build your own helm chart
3. Predefined
4. Pack helm chart
5. Push helm chart to chart repository (Github Container Registry)
ผู้อ่านสามารถย้อนกลับไปอ่านบทความก่อนหน้า ได้ที่ Helm life cycle management — chapter 2
มาลุยกันเลย …
1. Helm Package / helm chart
packag: คือการเก็บรวบรวม หรือห่อหุ้มสิ่งของ องค์ประกอบเข้าไว้ด้วยกัน
Helm package: ก็คือการรวบรวมองค์ประกอบของ Helm chart เข้าไว้ด้วยกัน ไม่ว่าจะเป็น helm template, helm value, note เป็นต้น
ซึ่งถ้าเราลองดึง helm package / helm chart ของสัก application มาดูด้วย
$ helm pull <chart_name / chart_repo_url> <flag>
eg.
$ helm pull bitnami/nginx --version 16.0.6
ผลลัพธ์: เราจะได้ zip ไฟล์ของ nginx ตาม version ที่เราระบุไว้
มาดูข้างใน zip ว่าประกอบด้วยอะไรบ้าง
$ tar -xvzf <zip_file.tgz>
eg.
$ tar -xvzf nginx-16.0.6.tgz
พบว่าใน package จะรวมไฟล์ไว้ ไม่ว่าจะเป็นส่วนที่อธิยาย chart , helm-value ที่จะเก็บค่าตัวแปลที่จะถูกไปแทนที่ใน k8s manifest file และส่วนที่เป็น template ที่จะเก็บรวบรวม k8s manifest file ซึ่งจะเห็นว่าพร้อมถูก apply บน k8s เลยเมื่อเราสั่ง helm install.
📍 section นี้ เราได้รู้ว่าจริงๆแล้ว helm pacakge / chart มันก็คือ zip ไฟล์ที่รวม config ของไฟล์ NOTE และ k8s manifest แพ็คไว้ด้วยกัน
2. Build your own helm chart
โอเครเราได้เห็น helm package / helm chart ของเจ้าดังๆไปแล้ว ถ้าเราต้องการจะสร้าง helm chart เองละ มาลุยกัน
สร้าง helm package / chart ด้วย helm cli
$ helm create <name>
$ helm create astronut
โอเคร helm จะ generate ขึ้นมาให้เราเต็มไปหมด ต่อด้วยลบสิ่งที่ไม่ยังไม่ต้องการ
เหลือแค่ note และแก้ไขข้อความใน NOTES.txt
จากนั้นเรามาสร้าง k8s deployment และ service manifest file
โดย image เราจะใช้ image simple nodejs app + express ที่เรา push ขึ้น dockerhub.io
//rocket-nodejs-app
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.status(200).json({message: "Hello World, this is rocket"});
})
app.get('/health', (req, res) => {
res.status(200).json({message: "I am healthy"});
})
app.listen(8080, () => {
console.log('Server is running on port 8080');
})
ต่อมาลอง validate ตัว helm-template เราด้วย
$ helm template <helm_template_path>
ถ้าไม่มี error แจ้ง และมีการคืนค่ามาให้แบบนี้แปลว่า template นี้ผ่าน ซึ่งการใช้ helm template cli จะสามารถช่วยเรา debug ค่าที่เป็น predefined ได้
ทดลอง install ตัว helm chart ใน local
$ helm install <release_name> <path>
เช็คด้วย helm ls: ไม่มี error แปลว่าเราสามารถ deploy helm chart — astronut ลงบน local Kubernetes ได้แล้ว 🙌🏻
มา เช็ค k8s object ต่อด้วย: ก็จะเห็นว่ามีการ apply ตัว deployment และ service ตามที่ถูกเขียนไว้ใน helm package / chart.
$ kubectl get all
มาดูว่าเราสามารถใช้งานตัว nodejs app เราได้ไหม ด้วย
//forward port ของตัว service ออกมา
$ kubectl port-forward svc/astronut-service 8080:8080
//จากนั้นก็ curl ยิง
$ curl localhost:8080
และ
$ curl localhost:8080/health
เย้ app ทำได้ปกติ 🍾
📍 section นี้เราสามารถสร้าง helm chart ของเราเองแล้ว ซึ่งเริ่มจากใช้ cli: helm create <…> เพื่อให้ generate ตัว standard format ของ helm chart มาให้ จากนั้นเราก็ลบแต่ที่เราต้องการ เพิ่มเติมด้วยสิ่งที่เอาอยากใส่ให้กับ chart นั่นคือ k8s deployment, service manifest file เมื่อเสร็จ เราก็ลอง validate และ install เล่นในเครื่องดูว่า helm chart เราทำงานได้ และแอปพลิเคชันใน chart ทำงานได้ตามที่คาดหวัง
3. Predefined
โอเครในเมื่อ section ที่แล้ว chart เราทำงานได้แล้ว แต่มีสิ่งหนึ่งที่ยังต้องปรับปรุงเพิ่มเติมนั่นคือค่า value ของแต่ละ field ใน deployment และ service file ยังเป็นค่าที่เรา hard code เข้าไปอยู่ ซึ่ง ถ้าเราต้องการที่จะ dynamic ในกรณีที่เรามีหลาย micro-service แล้วอยากให้ไปอ่านที่ helm-template เดียวกัน ก็สามารถทำได้โดยใช้ท่า “Predefined นั่นเอง”
predefiend: เป็นการกำหนดค่าไว้ ซึ่งจะไปดึงมาจาก helm value file และจะถูก replace ไปยัง k8s manifest file
//syntax predefined
{{ <predefiend_filed_name> }}
eg.
{{ .Release.Name }}
{{ .Release.Namespace }}
{{ .Chart.APIVERSION }}
{{ .Values.<key_name>.<sub_key_name>}}
...
มาลองเล่น predefined และ validate ต่อด้วย helm validate
เริ่มด้วยมากำหนดค่าที่ helm-value กันก่อน
จากนั้นก็ไปใช้ predefined ที่ deployment และ service
และเพิ่มเติมที่ NOTES ก็สามารถทำ predefined ได้เช่นกัน โดยจะดึงค่ามาจาก Chart.yaml
validate ด้วย helm template
จะเห็นว่าค่าที่เป็น predefined ( {{ … }}) เมื่อผ่าน helm template จะมีการเติมค่าจาก helm value ให้นี่ก็เป็นวิธีการ debug ว่า k8s manifest file เราอ่านค่าจาก helm value ได้จริงๆ
ปิดท้ายด้วยการ install มาเล่นบน local
โอเครจะเห็นว่าที่ NOTES มีการอ่าน predefine จาก chart.yml
และ k8s object ก็ทำงานได้ปกติ 🎉
📍 section นี้ เราจะได้เรียนรู้การใช้ predefined เพื่อจะเป็นการกำหนดค่าให้ k8s manifest ซึ่งการใช้ท่านี้จะช่วยให้เราสามารถ reuse ตัว k8s manifest ได้ เช่น กรณีที่ micro-service อยู่ใน domain / module เดี่ยวกัน ก็ใช้ความ dynamic ของการ set helm-value มาใช้ manifest เป็นไฟล์กลางในการ apply ได้
4. Pack helm chart
มาแพ็คทุกอย่างกันเถอะ ด้วย
$ helm package <chart_path>
eg.
$ helm package .
จะได้ zip ไฟล์ที่เป็นชื่อ chart ออกจาก และ version จะไปอ่านค่ามาจาก Chart.yaml
📍 section นี้สั้นๆ ด้วยการแพ็คของลง zip file ด้วยคำสั่ง helm package
5. Push helm chart to chart repository (Github chart repository)
เราจะ push ตัว helm package / chart ที่เราได้แพ็คใน section ก่อนหน้าไปบน Github container registry (GHCR) เพื่อ host ตัว helm chart ของเรา โดยเราต้องเตรียมดังนี้:
step1:
1.1)สร้าง token ที่มี privilege ในการ read, write, delete package
account > setting > developer setting > personal access tokens (PAT) > token
1.2 สร้าง repository ในที่นี้ขอใช้ชื่อ “charts”
step2: authentication GHCR
นำ token ที่สร้างจาก step1 มา authen ด้วย
//assign value
$ export HELM_PAT = <token>
//authen
$ echo $HELM_PAT | docker login ghcr.io -u <github_account_name> --password-stdin
ผลลัพธ์:
step3: push ตัว zip file (astronut-0.1.0.tgz) ขึ้นไปยัง GHCR ผ่าน protocol oci
$ helm push <chart_zip_file> oci//ghcr.io/<github_account_name>/<github_repo_name>
eg.
$ helm push astronut-0.1.0.tgz oci://ghcr.io/supanut1911/charts
ผลลัพธ์:
ไปดูว่าบน github มันอยู่ที่ไหน
account > packages tab
จะเห็นว่าตัว packge (helm package) จะโดนเอามาไว้ที่นี่ก่อนซึ่งตั้งเป็น private ให้ด้วย ซึ่งเราจะต้องเข้ากด link package นี้ไปยัง repository และ เปลี่ยน visibility เป็น public
จากนั้นเราก็ push code ขึ้น repo ก็จะได้ประมาณนี้ ที่มุมขาว ก็เป็นตัว package ที่เรา link เข้ามา
️⚠️ แต่ถ้าเราจะ helm repo add จาก GHCR มาลงที่เครื่อง หรือ cluster จะยังไม่ได้เพราะ oci ไม่มี registry
💡วิธีแก้คือ เราต้องสร้าง github page ขึ้นมา โดยจะใช้ท่า “orphan branch” โดยสร้าง branch ที่ชื่อว่า “gh-pages” ซึ่งข้างในจะมีแค่ไฟล์ index และ chart_zip_file (ซึ่งจะลบ file ทั้งหมด)
$ git checkout --orphan gh-pages
$ git rm -rf .
และ push ขึ้น branch gp-pages
$ git commit --allow-empty -m "feat: init chart repository"
$ git push origin gh-pages
เช็คที่ pages ที่ settings > pages เราจะได้ link ของ page ซึ่งจะใช้ link นี้เป็น *chart repository url
จากนั้นเราทำ index ด้วย
$ helm repo index . --url <github_page_url>
ผลลัพธ์: เราจะได้ index ไฟล์ขึ้นมาที่บอกรายละเอียดของ chart นี้ จะเป็นตัวกำหนดให้กับ chart repository url ซึ่งตอนที่เรา helm repo add มันก็จะวิ่งมาอ่านที่ index.yaml นี้ว่าควรจะโหลด zip file เวอร์ชั่นไหนนั่นเอง
เมื่อได้ไฟล์ index แล้วก็ push ขึ้น repo ได้เลย
$ git add -A
$ git commit -m "release: v0.0.1"
$ git push
บน github ก็จะได้ประมาณนี้
มาลอง repo add ที่ local กันด้วย chart_repo_url (จาก github page)
แล้วก็ลอง helm install
*ถ้าเรามีการ update version ของ chart เราก็ทำตาม step อีกครั้ง
ตัวอย่าง: “เราแก้ไข deployment file โดยเพิ่ม config readiness และ liveness เข้าไป”
- switch branch มาที่ branch หลัก
- แก้ไขปรับปรุง deployment.yaml, values.yaml
- update version ที่ Chart.yaml
//validate
$ helm template <chart_path>
//pack chart to zip
$ helm package <chart_path>
//rotate chart_zip_file out of project folder and push code
$ git add .
$ git commit -m "add readiness and liveness at deployment"
$ git push
// switch to branch gh-pages and add zip file to project folder
// $ helm repo index <chart_path>
//push to github repo
$ git add .
$ git commit -m "release: v0.2.0"
$ git push
จะเห็นว่าสิ่งที่เพิ่มขึ้นมี zip file และ config ที่ index.yaml
จากนั้นเพื่อจะให้ครบ loop เรามาลองสั่ง upgrade astronut chart ที่เรามีอยู่ในเครื่องตอนนี้ (อยู่ในเครื่อง version 0.1.0 ต้องการ version 0.2.0)
$ helm upgrade astronut-app astronut/astronut
เรียบร้อย upgrade ได้เหมือนกับ chart เจ้าอื่นๆ ด้วย
📍section นี้จะเป็น step ในการ นำ helm package / chart ไปขึ้น Chart repository ซึ่งเลือกใช้เป็น Github (GHCR) ซึ่งจะมี step ย่อยๆ ดังนั้น:
- เริ่มด้วยการ set up Github repository ให้พร้อม
- สร้าง Token Personal Access Token (PAT)
- authentication ด้วย token
- push ตัว chart_zip_file ขึ้นไปบน GCR ผ่าน oci
- จากนั้นก็ push code ของ helm ขึ้น repo
- สร้าง branch orphan เพื่อไว้เก็บ chart_zip_file ชื่อ “gh-pages”
- ^ ขั้นตอนนี้จะได้ github pages ซึ่งจะมี link มาให้ โดยเราจะใช้เป็น chart_repository_url
- ทำการสร้าง index.yaml ด้วย helm repo index <…>
- push index.yaml ขึ้น branch gh-pages
- ลอง helm repo add จาก chart_repository_url (aka github_page_link)
Conclusion
หัวข้อทั้งหมด ที่ได้เรียงลำดับมาให้ จะทำให้เล่นกับการสร้าง helm chart ของตัวเองได้อย่างครบ loop แล้ว ซึ่งบทความนี้จะเป็นท่าที่ใช้ Github Container Registry (GHCR) เป็น Chart repository ซึ่งมีตัวที่อยากลองเพิ่มคือ Gitlab, Nexus และ Chart museum อยู่ ซึ่งจะมาแชร์ในบทความต่อๆไปครับ
การบ้านส่งท้าย:
โจทย์“ถ้าใน branchgh-pages เรามีแค่ index (ไม่มี tgz) แล้วเราไปใช้ที่เก็บที่อื่นได้ไหม”
มีข้อสังเกตุว่าทำไมบางเจ้า เขาถึงไม่เก็บ chart_zip_file ไว้ที่ branch “gh-pages” เช่น ของ back stage
จะเห็นว่ามีแค่ file index เท่านั้น และเหมือนจะไปใช้ของที่อยู่ใน packages
เรามาดูกันว่าทำไมเขาถึงทำท่านี้ได้ ลองดูกันที่ index.yaml และลองเปรียบเทียบกับของเรา
สังเกตุที่ตัวแปร urls จะเห็นว่าของ back stage จะชี้ไปยังอีก url นึง (ชี้ไปที่ url ของ backstage/charts/packages แหละ) แต่ของผมชี้ไปที่ไฟล์ zip ในตัว repo
ซึ่งนั้นแปลว่าเราก็ไม่จำเป็นต้องมีไฟล์ chart_zip_file ใน repo ก็ได้ ให้ใช้ท่า แก้ไข url ของไฟล์ index.yaml โดยเปลี่ยนไปชี้ที่ package แทนนั่นเอง มาลองดูกัน
https://github.com/<github_account>/<chart_repo_name>/releases/download/<chart_name>-<chart_version>/<chart_version>.tgz
eg.
https://github.com/supanut1911/charts/releases/download/astronut-0.2.0/astronut-0.2.0.tgz
สร้าง release ของทั้ง 0.1.0 และ 0.2.0 เพื่อไว้ใช้ในเรื่องการ download zip file เน่อ
ลอง helm repo add ตัว version 0.1.0 และ upgrade เป็น latest version (0.2.0) กัน
เรียนร้อยสามารถ helm repo add, install และ upgrade ได้เหมือนของเดิมละ ท่านี้ก็จะตัว github ก็จะคลีนๆ หน่อย
จะเหลือแค่ไฟล์ index.yaml อย่างเดียว ละก็ตัว chart จะเรียกไปที่ packages และมี release เพื่อเป็นตัวชี้ไป Download ตัว zip ให้ถูกต้อง
คำตอบของโจทย์ข้อนี้คือ เราไม่ต้อง push ตัว chart_zip_file ไปเข้าที่ repo ได้ ซึ่งเราสามารถไปเรียกที่ packages ได้ ด้วย step ดังนี้คือ:
- แก้ไขไฟล์ index.yaml ของแต่ละ version ที่ field “urls” ให้ใส่เป็น path ของ Github packages แทน
- ที่ path github package จะเป็น /release/download/* จะเห็นว่า เราต้องสร้าง release ด้วย เพื่อที่จะสามารถฝาก chart_zip_file กับ release ได้ และในตอน helm install มันจะได้ไปดึง Download มาถูกไฟล์เน่อ
แค่ 2 step เป็นอันเสร็จพิธี ต่อจากนี้เราก็เล่น helm life cycle management กันต่อโลดดดด … 🙌🏻