ก้าวแรกกับ Kubernetes

ก่อนจะเริ่มต้นการเดินทางเข้าสู่ Kubernetes 

 

TLDL; ตั้งใจเขียนบทความเกี่ยวกับ Kubernetes มาซักพักนึง แต่ก็ผลัดมาตลอด สุดท้ายได้โอกาสเริ่มซะที บทความชุดนี้อาจจะไม่บอกครบทุกเรื่อง เพราะถ้าบอกทุกเรื่อง ผู้อ่านคงหาอ่านเอาเองได้จาก Official Document

ก้าวแรกกับ Kubernetes (อ่านว่า คู-เบอ-เน-เทส|ทีส) ไม่ใช่เรื่องของ Kubernetes แต่เป็นเรื่อง Container พูดถึง Container เราก็มักจะนึกถึง Docker ด้วยเหตุผลที่ว่า Docker เป็นรายแรกที่ทำให้ Container ป๊อบปูล่า ฮิตติดหู ผมเองก็ใช้คำว่า Docker กับ Container สลับกันในความหมายเดียวกัน (ออกตัวไว้ก่อนเลย)

แล้ว Container นี่มันอะไรยังไง? ลองมาเปรียบเทียบ Container กับรุ่นพี่ๆกันดู

 

รุ่นแรก

โบราณที่สุด (ที่ผมเคยใช้) เป็น Bare Metal เครื่องหนึ่งเครื่อง จะรันกี่โปรแกรม (App) ก็รันไป สำคัญที่ว่า Libraries (.dll, .a หรือ .so) กับพวกโปรแกรมพื้นฐาน ( awksedtar, etc) จะต้องไม่ชนกัน (Conflict) ถ้าชนกันก็ต้องหาทางแก้กันไป ไม่ก็เลือกรันแค่ App นึง ระบบแบบนี้มีข้อดีตรงที่ทำงานได้เร็วสุด เพราะว่ามีจำนวนส่วนต่างๆ (Layers) น้อยที่สุด ไม่มีส่วนไหนที่เกินจำเป็นพื้นฐานเลย (Zero Overhead) ข้อเสียคือจัดการลำบากสุด

Bare Metal

Bare Metal 

รุ่นสอง

เป็น Virtualization จาก Server เครื่องนึง เราสามารถสร้าง Server จำลองได้หลายเครื่อง และใน Server จำลองแต่ละเครื่องสามารถลง OS และโปรแกรมต่างๆได้โดยไม่ไปกวนกัน (Conflict) ระบบส่วนใหญ่ในปัจจุบันจะย้ายมาเป็นแบบนี้เกือบทั้งหมดแล้ว (นอกจากบางงานที่ยังจำเป็นต้องใช้ Bare metal)

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

Virtualization

Virtualization

รุ่นเรา

เป็นรุ่น Containerization ในรูปจะอยู่แถวขวาสุด เพิ่มชั้นสี่ส้มมาจัดการ Containers บางที่ก็รวม Guest OS, Hypervisor กับ Container Engine ไว้ด้วยกันเป็น Layer ก็มี แต่นั่นไม่ใช่แบบที่คนหมู่มากติดตั้ง ซึ่งจะเหมือนตามในรูปมากกว่า

Bare Metal vs Virtualization vs Containerization

Bare Metal vs Virtualization vs Containerization

แล้วมันดียังไง? แต่ละชุดของ App+Libs+Bins ไม่จำเป็นต้องรัน Guest OS ขึ้นมาโดยเฉพาะ แล้วเจ้า Guest OS เนี่ย จริงๆแล้วมันหนาเทอะทะมากกว่า Container Engine เยอะ พอมาใช้ Container เราจะสามารถรันหลายโปรแกรมที่เมื่อก่อนไม่สามารถรันในเครื่องเดียวกันได้ (Guest OS เดียวกัน)

ข้อดีอีกข้อคือ Container เนี่ยรันจาก Container Image (หลังจากนี้จะเรียกสั้นๆว่า Image) ซึ่งเป็นสิ่งเดียวกันตั้งแต่ครั้งแรกที่มันโดน Build + รันในเครื่องแรก + เครื่องสอง สาม สี่ห้า จนไปถึงเครื่อง Production ข้อดีข้อนี้จะทำให้ประโยคยอดฮิตกลายเป็นอดีต It works on my computer!

 

เริ่มเล่นละนะ

อารัมภบทมาซะเยิ่นยาว มาลองเริ่มเล่นกันซักที

ข้อหนึ่ง ลงโปรแกรม Docker Desktop โปรแกรมนี้ใช้ได้ทั้ง Mac และ Windows จากเว็บ Docker เอง โปรแกรมลงไม่ยาก ก็กดๆคลิกๆไปตามถนัดลงแล้วจะได้ icon ปลาวาฬแบกตู้ containers โผล่มาให้เห็น

เพิ่มเติมสำหรับคนที่ใช้ Windows Subsystem for Linux (WSL) สามารถใช้ Docker client ใน Linux ต่อเข้า Docker Desktop ที่รันใน Windows ได้ ใครไม่ใช้แบบนี้ ข้ามส่วนนี้ไปได้เลย 

ขั้นแรก config ให้ Docker เปิดพอร์ตรอไว้เลย

ก้าวแรกกับ Kubernetes

สเต็ปสองก็ชี้ Docker Host ไปที่ Docker Desktop

export DOCKER=tcp://127.0.0.1:2375

สุดท้ายก็ลง Docker CLI สำหรับ Ubuntu ลงด้วยคำสั่ง

sudo apt-get install docker-ce-cli 

ปิดท้ายด้วยคำสั่งพื้นฐานสำหรับ Docker

ถ้าเราคิดซะว่า Container เป็นเหมือน Virtual Machine (VM) เครื่องนึง คำสั่งที่เราต้องการก็ไม่มีอะไรมากไปว่า เปิด-ปิด-ต่อเข้าเครื่อง-ลบทิ้ง เพิ่มมาอีกนิดก็จะเป็น เปิด Firewall กับทำ Port Mapping ซึ่งเราอาจจะไม่ได้ทำใน VM

ส่วน Image ก็จะเหมือนการทำ Zip Archive หรือ ISO File แต่จะง่ายกว่า ซึ่ง Image จะทำเป็น Layer ตยช (ตัวอย่างเช่น) Layer C สร้างจาก Layer B (ซึ่งสร้างจาก Layer A อีกที) เรื่อง Image นี่เอาไว้เรามาว่ากันอีกที สำหรับตอนนี้ ลองใช้ Image ของคนอื่นไปก่อน (เหมือนกับที่เราไม่ได้สร้าง ISO File เอง แต่ดาวน์โหลด Ubuntu ISO มาใช้เพื่อลง Ubuntu Linux)

เปิดเครื่อง Web Server (Container)

$ docker container run -d nginx
ced7a1d474a10b6e4e0d2e23b47585d7772a481a6a8fd5b0b1aeca038b6ef05d

เรากำหนด -d เพื่อให้ Container รันด้านหลัง (Background/Detached) เราจะๆได้ ID เป็นชุด Hexadecimal Unique ID ชุดนึง ซึ่งเราสามารถเราไปใช้เลือกว่าจะสั่งงาน Container ไหนในตอนหลังได้

ดูรายชื่อ

$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ced7a1d474a1 nginx “nginx -g ‘daemon of…” About a minute ago Up About a minute 80/tcp ecstatic_hertz

คำหลังสุด (ecstatic_hertz) เป็นชื่อ Container ซึ่งถ้าเราไม่กำหนด Docker Engine จะตั้งมาให้เราอัตโนมัติ ถ้าผมลองสร้าง Container อีกอันด้วยคำสั่งเดิม ผมก็จะได้ชื่อใหม่ที่ไม่ซ้ำกับชื่อเดิมเสมอ เราสามารถกำหนดชื่อ Container โดยใช้พารามิเตอร์ --name เช่น docker container run -d --name web-server nginx

ปิดเครื่อง

$ docker container stop ecstatic_hertz
ecstatic_hertz

การกำหนดว่าจะปิด (และสั่งงานอื่น) สามารถใช้ชื่อหรือ ID ที่เราได้มาก่อนหน้าได้ การใช้ ID ยังพิเศษไปอีกตรงที่เราไม่ต้องพิมพ์ให้ครบก็ได้ แค่สามสี่ตัวอักษรก็เพียงพอ ขอแค่ให้มันกำหนด Container ได้ตัวเดียว เช่นในตัวอย่าง ผมสามารถปิด Container ได้ด้วยคำสั่ง docker container stop ced7 แต่ถ้าเราใช้ชื่อ จะต้องพิมพ์ให้ครบถ้วนทุกตัวอักษร

ถ้าเราลอง ls ดูจะไม่เห็น ecstatic_hertz แล้ว แต่จริงๆแล้ว ecstatic_hertz ยังอยู่ เราต้องเพิ่มพารามิเตอร์ -a เข้าไปแบบนี้ docker container ls -a จึงจะเห็นทุก Container รวมถึง Container ที่ซ่อนอยู่ด้วย

เปิดเครื่องพร้อมเปิด Firewall และทำ Port Mapping

$ docker container run -d -p 82:80 nginx
adbe9c6247cbb69a2493eb501178455c08f1b75ba32ab22ecd0abbf70b869619

เพิ่มพารามิเตอร์ -p 82:80 เพื่อจะ Map Port 82 ที่เครื่องของเราเข้าไปที่ Port 80 ของ Container ถ้าเรา docker container ls จะได้ข้อมูลเกี่ยวกับ Network แบบนี้ 0.0.0.0:82->80/tcp และถ้าจะ Map มากกว่าหนึ่ง Port เราสามารถกำหนด -p ได้หลายครั้ง

ต่อเข้าเครื่อง

$ docker container exec -it adbe sh

พารามิเตอร์ -it เพื่อกำหนดให้เป็น Interactive Terminal ตามด้วยชื่อหรือ ID ของ Container และคำสั่งที่จะรัน คำสั่งที่จะรันนี้จะ Path เต็มเช่น /bin/sh หรือจะใช้แค่ชื่อคำสั่งก็ได้

ลบ Container

$ docker container rm ced7

ถ้าเรามองว่า Container ก็เหมือน Mini VM ดังนั้น เราก็ควรจะลบ VM ที่เราไม่ได้ใช้ จะได้ไม่รก ถ้าจะลบหลายๆ Container เราสามารถใช้ ID หรือชื่อ ต่อๆกันไปตามที่ต้องการได้ หรือถ้าจะลบทุกๆ Container ที่หยุดทำงานไปแล้ว สามารถใช้คำสั่ง prune ได้

$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
84e245645e1e338a8e4fd70c95e964d5cf4c861d65ff33999adb5e8188e0a8d6
c8cc3a0e8ec9a0c0a4df10ef1c20d06a78c3c5c2625bad6f2d6257985b2a876d
b95ee9202ec4dff59c7fb658f304c042f4dc60536f98f17dd1535998e1d071d4
bde4f18ef33139e3f714b848ddc7bbf98f0fc58ae3f816f12bdae8b95f5543b8
Total reclaimed space: 27.38MB

แล้วถ้าจะ รัน Container ของ Ubuntu 20.04 ละ

$ docker container run -d ubuntu:20.04
7ecfa780a33541c7435ce385851084a4f9599f7cd8d602752254b5197274679e
$ docker container ls -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
7ecfa780a335        ubuntu:20.04        "/bin/bash"         8 seconds ago       Exited (0) 7 seconds ago                       quirky_clarke

อ้าว! ทำไม Status เป็น Exited ซะละ? ทุกๆ Container จะต้องเริ่มด้วยการรันคำสั่งหนึ่งคำสั่ง และคำสั่งนั้นจะต้องรันค้างไว้ ถ้าคำสั่งจบหรือมีปัญหาและหยุดการทำงาน Container นั่นก็จะตายไปด้วย

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

แบบแรก

$ docker container run -it ubuntu:20.04 bash
root@05b053dbc136:/# exit
exit
$ docker container ls -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS
05b053dbc136        ubuntu:20.04        "bash"              About a minute ago   Exited (0) About a minute ago                       heuristic_meitner

จะเห็นว่า Container หยุดการทำงานไปหลังจากที่เราออกจาก Shell ซึ่งเป็นคำสั่ง (Process) แรกและคำสั่งเดียวของ Container

แบบสอง

$ docker container run -d ubuntu:20.04 tail -f /dev/null
6f4a2225dc15c04e36bba5fee46c8e7dd5dd41574622f1001d81fe66cb78d670
$ docker container exec -it 6f4a bash
root@6f4a2225dc15:/# exit
$ docker container ls
CONTAINER ID        IMAGE               COMMAND               CREATED             STATUS              PORTS               NAMES
6f4a2225dc15        ubuntu:20.04        "tail -f /dev/null"   29 seconds ago      Up 28 seconds                           objective_fermi

คำสั่ง tail -f /dev/null ไม่กิน CPU และค้างไปอย่างนั้นจนกว่าเราจะสั่งให้มันหยุดเอง จากนั้นเราค่อยต่อเข้า Container เราสามารถต่อเข้าออกไปได้เรื่อย และปิด Container ตอนที่เราต้องการ


สำหรับ Docker Image เราจะกลับมาดูวิธีการสร้างกันอีกทีตอนหลัง (คุยกันตอนนี้มันจะยาว) ตอนนี้มาดูคำสั่งพื้นฐานที่ต้องรู้ซักสองสามคำสั่ง

  • ดู Image ที่อยู่ในเครื่อง docker image ls
  • ดาวน์โหลด Image มาจาก Docker Hub docker image pull nginx
  • ลบ Image ที่เราไม่ได้ใช้แล้ว docker image prune -f

แต๊ง แต๊ง หมดยก คราวหน้าเรามาต่อกันอีกที สัญญาว่าจะได้ลองเล่น Kubernetes กันละครับ

Tags

What do you think?

Related articles