Hike News
Hike News

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

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

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

งาน

ปีนี้เป็นปีที่ทำงานไม่เป็นชิ้นเป็นอันอีกที เริ่มแรกไปทำกับทีมที่ทำ Service เรือธงของบริษัท ซึ่งหัวหน้าก็หวังจะให้เข้าไปช่วยแก้ปัญหา แต่เหมือนเอาเข้าไปเข้าไปแล้วปัญหามันกลับมากขึ้น อาจจะเป็นเพราะลักษณะการทำงานคนละแบบกัน เกลียดอะไร ไม่อยากเจออะไร ก็เจอกับงานที่กำลังทำตลอด

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

สุดท้ายเหมือน Project นี้มันถูกหยุดเพราะเขาหาวิธีแก้ปัญหาที่ง่ายกว่าและใช้งบประมาณน้อยกว่าได้ก็เลยหันไปใช้วิธีแก้ปัญหานั้น ก็เลยโดนย้ายไปทำงานใหม่ งานใหม่ที่ว่าคือ Data Engineer ครับ งานที่ยังหลอนๆอยู่เลยว่าตำแหน่งนี้มันทำอะไรกันแน่วะ แต่พอมาคิดดูดีๆ ทำงานมา 10 ปี เราไม่เคยมีตำแหน่งห่าอะไรอยู่แล้ว แค่ได้รับงานมา ทำให้เป็นจริงให้ได้ ใช้งานให้ได้ แค่นั้น ดังนั้นขอแค่บอกว่าอยากได้อะไร ประมาณไหน เดี๋ยวก็ไปทำด้วยเล่ห์ด้วยกลด้วยมนต์แห่งโลกเสมือนทำให้มันออกมาให้ได้ ซึ่ง Project นี้ทำให้ได้เปิดโลกมาก ได้ไปเรียนรู้เกี่ยวกับ Hadoop และพี่น้องพ้องเพื่อนของมัน ซึ่งก็ถือว่าสนุกดีได้เรียนรู้ ได้จมอยู่กับปัญหาและปวดหัวเพราะถามใครไม่ได้เลย Chat GPT ก็ตอบไม่ได้ คิดว่าปี 2025 อีกหลายเดือนน่าจะต้องจมอยู่กับมันเนี่ยแหละ ถ้าโชคดีจบได้ ได้ขึ้น PROD ก็จะเป็นอีกหนึ่งงานที่ภูมิใจ

ดูหนัง

เนื่องจากปี 2023 ซื้อบัตร Mpass รายปีมา ทำให้ปี 2024 ได้ดูหนังตามสิทธิ์ที่ได้เกือบครึ่งปี ซึ่งบอกเลยว่าดูเอาคุ้มจริงๆ ดูแบบสัปดาห์ละ 3 - 4 เรื่อง คือทุกวันเสาร์ไม่ก็อาทิตย์จะไปดูหนังตั้งแต่รอบแรกดูจนครบทุกเรื่องที่มีฉายและเรื่องไม่ซ้ำที่เคยดูแล้ว ( Mpass ดูหนังซ้ำไม่ได้) ซึ่งเมื่อบัตร Mpass หมดอายุก็มานั่งดูว่าดูหนังไปกี่เรื่อง สรุปแล้วดูหนังไปทั้งหมด 112 เรื่อง ซึ่งถ้าคิดว่าค่าดูหนังเรื่องละ 100 บาท คำนวณแล้วจะเป็นเงิน 11,200 บาท แต่เมื่อคุณใช้ Mpass 1 ปีจะเสียเงินแค่ 3,600 บาท ซึ่งถือว่าคุ้มจัดๆเลย แต่พอหมดอายุก็ไม่ได้ต่อเพราะคิดว่าน่าจะพอละ เราดูหนังมาเยอะมาก เยอะกว่าใช้เวลาทั้งชีวิตดูอีก เลยคิดว่าพักไปหาอย่างอื่นทำบ้าง

ใครสนใจอยากรู้ว่าผมดูหนังเรื่องอะไรบ้าง ความรู้สึกเป็นยังไง สามารถดูได้ที่ Playlist : Youtube นี้

ช่อง Youtube

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

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

ความรัก

ปีนี้เป็นปีที่หาเรื่องเจ็บตัวเพราะความรักโดยแท้ แต่ก็มีดีและไม่ดี (แต่ไม่ดีมันเยอะกว่า แถมมันยังเจ็บอยู่ในขณะที่เขียนอยู่)

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

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

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

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

แต่ใครจะไปรู้อนาคตล่ะ ไม่แน่ปี 2025 อาจจะหน้ามืดตามัวหรือตาสว่างไปจีบสาวหาเรื่องเจ็บตัวอีกก็ได้

กิจกรรมใหม่

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

มีอีกหลายเรื่อง

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

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

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

สุดท้ายนี้ขอจบด้วยเพลงนี้ เพลงเดิมเหมือนปีที่แล้ว

Nobody knows

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

พบรัก

ชีวิตเธอดีอยู่แล้ว

พื้นที่ว่าง

อิฐจะเอา NTR

จัดฉาก

APT. x อะนันตะปัดชะเย

เจ็บจนไม่เข้าใจ

ไม่เคย

มาใช้ SonarLint ตรวจสอบ Code ของเรากันเถอะ

มาใช้ SonarLint ตรวจสอบ Code ของเรากันเถอะ

SonarLint

ปัญหาเรื่องการเขียน Code

ในการเขียน Code นั้นทุกคนคาดหวัง Code ที่ดี ซึ่ง Code ที่ดีนั้นประกอบไปด้วยหลายอย่าง ไม่ว่าจะเป็นการอ่านง่าย แก้ไขง่าย ไม่เปิดช่องโหว่เรื่องความปลอดภัย ไม่มี Bug ซึ่งการที่จะเขียน Code ที่ดีได้นั้นคนที่เขียนจะต้องมีประสบการณ์ ต้องรู้ว่าทำไมเขียนแบบนี้ดี เขียนแบบนี้ไม่ดี ซึ่งประสบการณ์ส่วนใหญ่เนี่ยมักจะได้มาจากการเกิดปัญหา หรือถ้าคุณโชคดีหน่อยคุณอาจจะมีคนมาทำ Code Review ให้ แล้วบอกคุณว่า Code นี้ดี Code นี้ไม่ดี ซึ่งคนมา Review นั้นก็คือเหล่าผู้มีประสบการณ์ซึ่งนั่นก็คือเหล่า Senior Developer

แต่คำถามคือ Senior Developer จะรู้เหรอว่า Code ที่ดีและไม่ดีทั้งหมดมีอะไรบ้าง ถ้าตอบตามความจริงเลยคือไม่มีใครรู้หรอกครับ ทุกคนรู้กันอย่างละนิดละหน่อยจากงานที่ตัวเองเจอ อีกทั้งในชีวิตจริงไม่มีใครมานั่งทำ Code Review ทุกส่วนหรอกครับ (วงการ Open Source อาจมี แต่ระดับบริษัทเล็กๆที่งานยุ่งกันจะตายไม่มีใครมานั่ง Code Review กันทุกส่วนหรอกครับ ) แล้วถ้ามองให้แรงกว่านี้หน่อย การ Review อะไรซ้ำๆ เห็นอะไรที่ผิดซ้ำๆ แล้วต้องมานั่งบอกซ้ำๆ หลายๆรอบ คุณไม่คิดว่ามันเสียเวลาแบบเปล่าประโยชน์เหรอกับการทำอะไรซ้ำๆ อีกทั้งมันยังต้องเสียเวลารอกันไปมาระหว่างคนเขียนกับคน Review อีก

แล้วเราจะแก้ปัญหานี้ยังไง

ปัญหาของเราคือ

  1. เราไม่สามารถให้คนมาทำ Code Review ตรวจสอบ Code ของเราได้ตลอดเวลา ซึ่งต่อให้ทำได้ก็เสียเวลา

  2. คนทำ Code Review ไม่รู้ทั้งหมดหรอกว่า Code ที่ดีไม่ดีทั้งหมดมีอะไรบ้าง

จากปัญหาที่ว่าถ้าเราสร้าง Software ตัวหนึ่งขึ้นมา ซึ่ง Software ตัวนี้สามารถกำหนดรูปแบบของ Code ที่ไม่ดีว่ามีหน้าตาอย่างไร และ Software ตัวนี้สามารถอ่าน Code ของคุณแล้วหาว่า Code ของคุณมีตรงไหนบ้างที่ตรงกับรูปแบบของ Code ที่ไม่ดี

เพียงเท่านี้คุณก็สามารถสั่งให้ Software ตัวนี้ทำงานได้ตลอดโดยคุณไม่ต้องเสียเวลารอพวก Senior Developer มา Review คุณจะทำตอนไหนก็ได้ ทำกี่ครั้งก็ได้ อีกทั้งรูปแบบของ Code ที่ไม่ดีสามารถเพิ่มได้เรื่อยๆ ดังนั้นถ้าเรามี Community ที่ใหญ่พอมาช่วยกันใส่รูปแบบของ Code ที่ไม่ดีเรื่อยๆตามการเจอนั่นก็เท่ากับว่าเราก็แทบจะรู้รูปแบบของ Code ที่ไม่ดีทั้งหมด

Software ที่ว่านี้มีอยู่บนโลกแล้วคุณไม่ต้องสร้างเองก็ได้

คุณอ่านไม่ผิดครับ Software นั้นมีอยู่แล้วมันคือ static code analyser ซึ่งมีมากมายหลายยี่ห้อตามภาพเลย ซึ่งคุณสามารถไปเลือกใช้ได้เลยตามใจ แต่วันนี้ที่จะมาแนะนำให้ใช้กันคือ SonarLint ครับ

ทำไมถึงเลือกใช้ SonarLint

  1. ฟรี

    SonarLint มันฟรีครับ ใช้ได้โดยไม่เสียเงินสักบาท

  2. รองรับหลายภาษา

    ตัว SonarLint ลงทีเดียวใช้ได้กับหลายภาษาเลย ดังนั้นไม่ต้องปวดหัวเวลาต้องเขียนหลายภาษาแล้วต้องไปไล่ลง software หลายๆตัว

  3. สามารถใช้ได้กับ IDE ยอดนิยมได้หลายตัว

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

  4. สามารถทำงานได้โดยไม่ต้องต่อ Server

    เนื่องจาก Static code analyser บางตัวนั้นเราต้องส่ง Code ไปทำการตรวจสอบบน Server ซึ่งแปลว่าเราต้องทำการลง Software ที่เป็น Server ซึ่งนั่นตามด้วยต้องหาเครื่อง Server ลงโปรแกรม จัดการ Network และอีกมากมายก่อนถึงจะสามารถทำการตรวจสอบ Code ได้ แต่ SonarLint สามารถ Install ที่เครื่องตัวเองแล้วใช้งานได้เลย

วิธีการ Install

ตอนนี้ผมจะแนะนำ SonarLint บน IDE สองตัวคือ Intellij และ Visual Studio Code

Intellij

ทำตามลำดับดังภาพจากนั้นทำการกดปิด Intellij แล้วเปิดใหม่ เพียงเท่านี้คุณก็สามารถใช้งาน SonarLint บน Intelij ได้แล้ว


Visual Studio Code

ทำตามลำดับดังภาพจากนั้นทำการกดปิด Visual Studio Code แล้วเปิดใหม่ เพียงเท่านี้คุณก็สามารถใช้งาน SonarLint บน Visual Studio Code ได้แล้ว

ใช้ Sonarlint ตรวจสอบ Code

ในส่วนนี้จะเป็นการแสดงวิธีการใช้งาน SonarLint ทำการตรวจสอบ Code โดยจะแสดงให้ดูใน Intellij และ Visual Studio Code

ใช้ Sonarlint ตรวจสอบ Code บน Intellij

สมมติผมเขียน Code Java ประมาณนี้

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestSonarlint {

public byte[] readFileToByteArray(String path) {

try {
FileInputStream fileInputStream = new FileInputStream(path);
return fileInputStream.readAllBytes();
} catch (IOException e) {
return null;
}
}

}

หากคุณเคยเขียน Java มาระดับหนึ่งคุณจะรู้เลยว่า Code ส่วนนี้มีส่วนไม่ดีตรงไหน แต่หากคุณไม่เคยเขียน Java คุณจะบอกว่าก็ไม่มีอะไรผิดนี่ คราวนี้เรามาลองใช้ SonarLint ตรวจสอบ Code ตามภาพ

  1. คลิกขวาเลือกไฟล์หรือ folder ที่ต้องการตรวจสอบ
  2. ทำการเลือก SonarLint และ Analyze With SonarLint

ซึ่งเมื่อ Scan เสร็จจะได้ผลลัพธ์ดังภาพ ซึ่งจากภาพจะเห็นว่าตัว SonarLint ได้แสดงผลลัพธ์การตรวจสอบ Code ให้กับเรา โดยเขาบอกว่า Resource ของเราไม่ได้ปิด พร้อมมีตัวอย่าง Code ที่มีลักษณะตรงตาม Pattern (จากภาพคือเหมือนกันคือเปิดใช้งาน Resource แล้วไม่ปิด)

และ SonarLint เขาไม่ได้แค่บอกว่า Code เรามีปัญหาอะไร เขาบอกแนวทางการแก้ไขปัญหาด้วย (ซึ่ง Code มีปัญหาเพราะไม่ได้ทำการปิด Resource ซึ่งอาจทำให้เกิดปัญหาคนมาใช้งาน Resource ต่อไม่ได้ หรือ อาการบางอาการที่เกี่ยวข้องกับการใช้งาน Resource พร้อมกัน)

ซึ่งผมสามารถแก้ไข code ของผมได้ประมาณนี้

1
2
3
4
5
6
7
8
9
10
11
12
public class TestSonarlint {

public byte[] readFileToByteArray(String path) {

try (FileInputStream fileInputStream = new FileInputStream(path)) {
return fileInputStream.readAllBytes();
} catch (IOException e) {
return null;
}
}

}

ซึ่งจะเห็นว่าส่วนที่แจ้งเตือนว่า Code เกี่ยวกับการปิด Resource ได้หายไปแล้ว (จริงๆยังเหลืออีกข้อที่ผมน่าจะตามไปแก้ ซึ่งเขาก็บอกแล้วว่าให้ return empty byte array แทนการ Return null)

ใช้ Sonarlint ตรวจสอบ Code บน Visual Studio Code

สมมติผมเขียน Code javascript ประมาณนี้

1
2
3
4
5
6
7
8
function findEpisode(data ) {
var splitText = data.split(":");
return parseInt(splitText[1]);
}

console.log(findEpisode("doctorwho:01"))
console.log(findEpisode("doctorwho:02"))
}

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




  1. ทำการเอาเมาส์ไปชี้ตรงจุดที่ code มีปัญหาโดยดูได้จากการมีเส้นสีเหลืองอยู่ข้างใต้
  2. จากนั้นจะมี pop up ขึ้นมาให้เลือกที่ Quick fix
  3. ในส่วนนี้คุณสามารถกดเลือก Quick fix ได้เลยเพื่อให้ Visual Studio Code ทำการแก้ code ให้ แต่ในที่นี้เราจะเลือก Opendesctiption เพื่อดูว่าทำไมมันถึงเป็น Code ที่มีปัญหา (Code ผมมีปัญหาเพราะประกาศด้วย var ซึ่งมันสามารถ access ได้ในระดับ function scope นั่นแปลว่า แม้อยู่ในคนละ scope ก็สามารถ access ค่าที่ประกาศด้วย var ได้ขอแค่อยู่ใน function เดียวกัน )

ในส่วนนี้จะเห็นว่าหน้าตาคล้ายๆกับในตัว Intellij ซึ่งไม่ต้องแปลกใจครับ เปิดใน IDE ไหนก็จะคล้ายๆกันหมดเพราะ sonarlint เขามี Rule ( Pattern ของ Code ที่มีปัญหา ) เกี่ยวกับปัญหาไว้ IDE ก็แค่ไปเอา Rule นั้นไปตรวจสอบและแสดงสาเหตุและวิธีการแก้ คุณสามารถไปดู Rule ทั้งหมดได้ที่ https://rules.sonarsource.com/

ซึ่งจากการอ่านผมเลือกที่จะแก้ code เป็น

1
2
3
4
5
6
7
8
function findEpisode(data ) {
let splitText = data.split(":");
return parseInt(splitText[1]);
}

console.log(findEpisode("doctorwho:01"))
console.log(findEpisode("doctorwho:02"))
}

เพียงเท่านี้เรา Code ของผมก็ไม่มีปัญหาแล้ว

เราได้อะไรจากการใช้ SonarLint ตรวจสอบ Code

  1. ได้ Code ที่ดีตามมาตรฐานของ SonarLint

    เมื่อเราใช้ SonarLint ตรวจสอบ Code และแก้ไขตามที่ SonarLint บอก เราย่อมได้ Code ที่ดีตามมาตรฐาน Sonarlint ( อย่าลืมว่ามันอาจจะไม่ใช่ Code ที่ดีที่สุด เพราะกฏทั้งหมดมาจาก Community ของ SonarLint ไม่ได้รวมจากที่อื่น อีกทั้ง บางอย่างที่ Community ของ SonarLint บอกว่าไม่ดี แต่ที่อื่นอาจจะบอกว่าดีก็ได้ หรือที่ SonarLint บอกว่าดี ที่อื่นอาจจะบอกว่าไม่ดีก็ได้ )

  2. ได้ความรู้เกี่ยวกับการเขียน Code ว่า Code ที่ไม่ดีเป็นยังไง

    จากการที่เราใช้ SonarLint ทำการตรวจสอบ Code เราจะเห็นว่าตัว SonarLint บอก Rule พร้อมบอกเหตุผลว่าทำไม Code ถึงมีปัญหา เช่น จากตัวอย่างของ Java เราจะรู้ว่าการไม่ปิด Resource นั้นจะทำให้เกิดปัญหา หรือ การประกาศ var ของ javascript นั้นอาจก่อให้เกิดปัญหาอะไร ซึ่งตรงนี้ถือเป็นจุดสำคัญจุดหนึ่งเพราะเราได้เรียนรู้ว่าปัญหาต่างๆเกิดจากอะไร เวลาเราเขียนอะไรที่ทำคล้ายๆกันเราจะรู้ว่ามันอาจก่อให้เกิดปัญหา เช่น เรารู้ว่าการประกาศ var นั้นทำให้เกิดปัญหาการ access ตัวแปรข้าม Scope ดังนั้น ถ้าเราเขียน Code ที่ใช้ตัวแปร Global นั่นก็แปลว่ามันอาจจะเกิดปัญหาได้เช่นกัน ซึ่งนั่นจะทำให้เราเกิดคำถามว่า เราจำเป็นต้องใช้ตัวแปร Global จริงๆหรือ อีกตัวอย่างหนึ่งคือเรารู้ว่าการใช้งาน Resource แล้วต้องจัดการให้เรียบร้อยไม่ว่าจะเกิดอะไรขึ้น ดังนั้นเมื่อเราเขียนโปรแกรมจองตั๋วเครื่องบิน (ซึ่งเป็นการใช้งาน Resource) เราจะเริ่มระวังว่า ถ้าจองตั๋วไม่สำเร็จเราจะจัดการ Resoruce ยังไง ถ้าโปรแกรมเกิดตายระหว่างกำลังจองตั๋วอยู่ สภาพของตั๋วจะเป็นยังไง
    ซึ่งสิ่งที่พูดมาทั้งหมดนั้น SonarLint บอกเราไม่ได้เลยเพราะมันไม่ได้อยู่ใน Rule ของ SonarLint แต่เรารู้ได้เพราะเรารู้ว่าปัญหาเกิดจากอะไร

Code Review ยังจำเป็นอยู่ไหม

บางคนอาจจะคิดว่าเราใช้ SonarLint แล้ว เราไม่จำเป็นต้องมี Code Review แต่จริงๆแล้ว Code Review ยังจำเป็นต้องทำอยู่ครับเพราะ

  1. มันมี Code ที่ไม่ดีที่ไม่อยู่ใน Rule ของ SonarLint

  2. Code ผ่าน SonarLint แต่ไม่ผ่านมาตรฐานของ Code base นั้น

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

  3. Code ไม่เป็นไปตาม Requirement ไม่ถูก Business Logic

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

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

แล้วเราจะใช้ SonarLint ไปทำไมในเมื่อก็ต้องทำ Code Review

ที่เราใช้ SonarLint เราทำเพื่อช่วยให้คนเขียน Code จัดการกับ Code ที่ไม่ดี ที่มี Rule อยู่บน SonarLint ซึ่งเมื่อจัดการหมดเนี่ย เราก็แทบจะเชื่อได้แล้วว่า Code เหล่านี้ไม่มีปัญหาที่พบได้ทั่วไป พอเวลาทำ Code Review เราจะได้ดูเฉพาะส่วนอื่นเช่น Requirement และ Business Logic ถูกต้องไหม Code ที่เขียนเป็นไปตามมาตรฐาน Code Base ไหม

ซึ่งตรงการทำ Code Review นี้ก็อยู่ที่แต่ละบริษัทจะเลือกเลยว่าจะทำไหม บางบริษัท Project นึงใช้คนเดียวเขียน ดังนั้นมันก็ไม่มีความจำเป็นที่จะต้องมาตรวจสอบ Requirement & Business Logic เพราะคนเขียนกับคนรับ Requirement มาเขียน Code เป็นคนเดียวกัน มันมีความจำเป็นอะไรต้องมาตรวจสอบ หรือ Requirement & Business Logic สามารถถูกตรวจสอบได้ตอน Test อยู่แล้ว ( แล้วจะทำไปทำไม เพื่ออะไร ) หรือ บาง Project นั้นใช้ Developer ที่เก่งอยู่แล้วรู้แล้วว่าอะไรดีอะไรไม่ดี มาตรฐาน Code Base เป็นยังไง ดังนั้นก็ไม่มีความจำเป็นอะไรที่จะต้องมานั่ง Review กันเอง (คำพูดเดิมครับ ทำไปทำไม ทำไปเพื่ออะไร)

สรุป

สำหรับบทความนี้เราได้เรียนรู้ปัญหาเรื่องการเขียน Code เราพยายามหาวิธีแก้ปัญหากันว่าจะแก้ยังไง และเราได้รู้ว่ามีคนเขียน Software ที่แก้ปัญหาเหล่านี้ไว้แล้วมากมาย ซึ่งในบทความนี้เราเลือกใช้ SonarLint มาเป็นตัวตรวจสอบ Code เราได้เรียนรู้วิธีใช้ SonarLint บน Intellij และ Visual Studio Code ว่าใช้งานยังไง และสุดท้ายเรา ได้เรียนรู้ข้อจำกัดของ SonarLint ว่ามันทำอะไรไม่ได้บ้าง สำหรับบทความนี้ก็ขอจบเพียงเท่านี้ครับ สวัสดีครับ

ความทรงจำปี 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
17
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 กันว่าคืออะไร