ทำความรู้จักกับ Cloud Run โฮสเว็บด้วย Container แบบไม่ต้องแตะ Infrastructure กันไปเลย

เมื่อต้นปีที่ผ่านมา ผมได้พูดถึง Google Cloud Functions กันไปแล้วในช่วงท้ายของ Serverless Big Data Architecture ว่ามันใช้งานง่ายและสะดวกขนาดไหน แต่ก็มีข้อจำกัดอย่างหนึ่งของ Google Cloud Functions ว่า จะต้องเขียนด้วยภาษาที่มันรองรับเท่านั้น ซึ่งตอนนี้ก็มี Python, ES, Golang และในงาน Cloud Next ’19 ที่ผ่านมา Google Cloud Platform ก็เปิดตัวของเล่นใหม่ให้ผมต้องลุกขึ้นปรบมือเลย เพราะอยากได้แบบนี้มานานแล้ว นั่นก็คือ Cloud Run นั่นเอง หลังจากได้เล่นมานิดๆ หน่อยๆ วันนี้จะพาแนะนำในเบื้องต้นว่า Cloud Run คืออะไร เหมาะสมกับงานประเภทไหน และมาทดลองใช้งานกัน


Google Cloud Run คืออะไร?

TL;DR: Cloud Run เอา Container Image ที่เป็น HTTP ของเราไปรันให้ และจัดการบริหารเรื่องทุกอย่างให้หมดเลย คิดเงินตามจำนวน Request และการใช้งานจริง

สำหรับท่านที่ยังไม่รู้จัก Container อาจจะสับสนหน่อย ผมสรุป Container ให้ฟังก่อน ซึ่งผมจะไม่ขอลงลึกมาก เอาให้เข้าใจ Concept พอนะครับ การสร้าง Container ขึ้นมา ให้มองว่ามันคล้ายๆ กับการทำ exe บน Windows นั่นล่ะ เวลาเราได้ exe มา เราจะเอาไปรันที่ไหนก็ได้ เช่นกัน พอเราสร้าง Container ขึ้นมา เราจะเอามันไปรันบนเครื่องไหนก็ได้ที่มี Docker จะได้ผลลัพธ์เหมือนกันหมด ความต่างมันอยู่ที่ว่า โลกของการเขียนโปรแกรมหลายครั้งมันต้องใช้ตัว Executor ระดับ OS เช่น PHP, Python, Node เราจะต้องลงโปรแกรมพวกนี้ก่อนถึงจะรัน Code ได้ แล้วโปรแกรมพวกนี้ก็มี Version อีก บางทีโปรแกรมเราต้องใช้กับ Version หนึ่งๆ เท่านั้น ดังนั้นเราก็เลยจับตัว Executor เข้าไปใส่รวมไว้ในสิ่งที่เรียกว่า Image พร้อมกับ Source Code ซะเลย ทีนี้เวลา Docker มันเอา Image ไปสร้าง Container มันจะได้โปรแกรมหน้าตาแบบเดียวกัน ทำงานเหมือนกันเป๊ะๆ และสามารถเอาไปใช้งานได้ในทุกที่ๆ มี Docker นั่นเอง

ทีนี้ในเมื่อเราสามารถทำให้ Container มันพร้อมรันเป็น Web Server ได้แล้ว ไม่ว่าจะเขียนมาด้วยภาษาอะไร สุดท้ายมันก็ใช้งานได้ผ่านทาง HTTP ทั้งหมด แล้วทำไมถึงจะไม่ทำให้มันจัดการบริหารง่ายขึ้นล่ะ คือเอาไปเสียบไว้ในโปรแกรมที่จะ Spawn Container ขึ้นมาให้โดยอัตโนมัติ แล้วก็เอา Load Balance เพื่อกระจายงาน ไปจนถึงถ้ามีโหลดมากๆ ก็เพิ่มเครื่องให้โดยอัตโนมติเข้าไป ก่อนหน้านี้ก็จะมี Kubernetes ซึ่งมันต้อง Config อะไรวุ่นวายมาก ตอนหลังมี Knative เกิดขึ้นมาอีก เราแค่เอา Container Image URL ไปเสียบ แล้วบอกให้มันตั้งค่า Environment ก็พอ ที่เหลือมันจัดการให้หมดเลย แต่ Knative ต้องรันบน Kubernetes อยู่ นั่นหมายความว่า เราจะต้องมี Cluster ของ Kubernetes ไว้ด้วยนั่นเอง แล้วจะเป็นไปได้ไหมที่เราจะไม่ต้องเปิด Cluster ของตัวเอง แล้วไปอาศัย Cluster ที่เปิดเอาไว้ให้บริการร่วมกัน (อารมณ์เหมือน Shared Kubernetes Cluster อะไรงั้น)

Google ก็เลยเอา Knative มาทำต่อให้ง่ายขึ้นไปอีก เพราะในเมื่อตัว Cloud เองก็ต้องเตรียมทุกอย่างไว้ให้ผู้ใช้บริการอยู่แล้ว ความ Auto Scale เพิ่มลดเครื่องเอง คือไหนชั้นก็ต้องเปิดใช้ Kubernetes อยู่แล้ว งั้นฉันก็เปิดเองแล้วเอาไปให้ผู้ใช้บริการอื่นใช้งานได้เลยไง สร้าง User Interface ให้ผู้ใช้สามารถใช้งานได้ง่ายๆ ผ่านทาง Web Console ก็ได้เรื่องละ นอกจากนั้นไหนๆ ก็ต้องเปิดไว้อยู่แล้ว ก็คิดเงินลูกค้าเฉพาะตอนที่เขาใช้งานจริงด้วย จะได้แฟร์กับทุกฝ่าย แล้วก็แอบเพิ่มค่าผ่านทางเข้าไปด้วย สำหรับคนใช้น้อยๆ ก็สบาย จ่ายเงินค่าใช้งานตามจริง ส่วนคนที่ใช้เยอะๆ ก็ควรไปตั้ง Cluster เอง ก็จะได้ราคาที่ดีกว่า ซึ่งก็ยังมี Cloud Run สำหรับคนตั้ง Cluster ใช้เองให้อีกด้วย ราคายังไม่มา แต่ก็น่าจะคิดเพิ่มเติมไปไม่มากเพราะคนตั้ง Cluster เองต้องจ่ายค่า VM อยู่แล้ว

สรุปก็คือ Cloud Run เอา Container ที่เปิดให้ใช้งานผ่านทาง HTTP ของเราไปรันให้ ทำทุกอย่างที่เหลือให้เราหมดเลย และคิดเงินตามการใช้งานจริงแบบ Cloud Functions ถ้า Deploy ขึ้นไปแล้วไม่ได้ใช้งาน ก็ไม่ต้องเสียเงิน (แต่ถ้าโดน DDoS ก็จ่ายอ่วมแน่)


มันต่างจาก App Engine ยังไง?

App Engine นี่เกิดมาตั้งแต่ต้นละ ก่อนจะมี GCP ซะด้วยซ้ำ เป็นโลกของการรัน Container เหมือนกันเป๊ะ ถ้าถามว่ามันต่างจาก App Engine ยังไง ที่เห็นชัดๆ เลยก็คือ App Engine จะมีให้เลือกใช้งานอยู่ 2 ประเภทคือ Standard กับ Flexible

สำหรับ Standard เราจะต้องใช้งาน Container ที่ Google ให้มา ซึ่งมันถูก Optimize มาให้เหมาะกับการรันบนสภาพแบบนั้น ทำให้สามารถ Scale จาก 0 to N ได้ คือเวลาไม่มีผู้ใช้นานๆ มันก็จะปิด Instance ลงไปทั้งหมดเลย

ส่วนประเภท Flexible นั้น เราสามารถเอา HTTP Container อะไรไปรันก็ได้เหมือนกับ Cloud Run เป๊ะ แต่เราจะต้องมี Instance เปิดเอาไว้อย่างน้อย 1 ตัว ในขณะที่ Cloud Run มันก็ยังสามารถทำ Scale 0 to N ได้อยู่

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


มันต่างจาก Cloud Functions ยังไง?

Cloud Functions นี่เกิดมาตอนหลัง เป็นโลกแห่ง Serverless อย่างแท้จริง แต่สิ่งที่เราเอาใส่ลงไปคือ Code ไม่ใช่ Container ดังนั้นเวลาใช้งาน Cloud Funtions เราจึงถูกจำกัดภาษาอยู่แค่ Golang Node.js และ Python ในขณะที่ Cloud Run เอา Container ที่เขียนด้วยภาษาอะไรก็ได้ขึ้นไปรัน ขอให้มันใช้งานผ่านทาง HTTP ได้ก็พอ

นอกจากนั้น Cloud Functions ยังถูกออกแบบมาเป็น Microservice โดยแท้จริง การใช้งานจริงมักจะเป็นการบอกว่า Function นี้ ใช้สำหรับทำสิ่งนี้เท่านั้น มันไม่ควรมีสิ่งอื่นไปปะปน แต่ Cloud Run ออกแบบมาให้รัน Container ซึ่งมันคือโปรแกรมทั้งตัว มันจึงเรียกแต่ละหน่วยที่รันของมันว่า Service ซึ่งนั่นคือมันประกอบด้วยหลาย Functions อยู่ข้างใน

ใน Cloud Functions ถ้าเราใช้งานผ่านทาง HTTP เราจะได้ URL เน่าๆ มา 1 อันไว้เรียกใช้งาน แต่ Cloud Run เราสามารถกำหนด Custom Domain ได้ด้วย ทำให้เราใช้เป็น Hosting แบบไม่มี Server ทั้งตัวได้เลย

แต่ Cloud Functions ก็มีข้อเหนือกว่าอย่างหนึ่ง คือ มันสามารถถูก Trigger ให้ทำงานได้จากหลายช่องทางมาก เช่น เราสามารถสั่งให้มันทำงานเมื่อมีการเปลี่ยนแปลงใน Google Cloud Storage Bucket แล้วมันจะเอาไฟล์ที่เปลี่ยนแปลงมาประมวลผล หรือจะใช้งานผ่านทาง Pub/Sub ก็ได้ (เริ่มจะยืดยาว) มันจะเหมาะอย่างยิ่งในการเอาไปทำ Pipeline ต่างๆ ให้คิดอะไรนิดๆ หน่อยๆ แล้วก็เก็บข้อมูลลง DB ไป ในขณะที่ผมมองว่าอนาคต Cloud Run ก็น่าจะทำได้เช่นกัน แต่ตอนนี้คือยังทำไม่ได้ (ให้มันเปิดใช้งานนอก US ให้ได้ก่อนเถอะ)


Web Application ประมาณไหนถึงเหมาะกับ Cloud Run?

ความจริงโปรแกรมที่จะเอาไปรันบน Cloud Run นั้น ก็ไม่ต่างอะไรกับโปรแกรมที่จะไปใช้กับ Container เลย ซึ่งก็จะมีลักษณะคร่าวๆ ดังนี้

  • ถ้าเป็น Stateless Application จะสบายมาก ไม่ต้องทำอะไร เหมาะสุดเลยงานนี้
  • แต่ถ้าเป็น Stateful Application จะต้องมีการเก็บ State ผ่าน DB ที่อยู่ข้างนอก Container โดยใน GCP จะมี Managed DB ให้ใช้ เช่น Cloud SQL, Cloud Datastore เป็นต้น และทำการ Implement โดยใช้วิธีการเช่น JWT เพื่อจะได้รู้ว่า ตกลงคนที่ Request เข้ามาคือ User ไหนกันแน่ หรือวิธีการอื่นๆ ที่เก็บ Session ผ่านทาง DB แทนที่จะเป็นใน Container เพราะว่ามีโอกาสเสมอที่ User จะเข้ามาแล้วเจอ Container คนละตัวกัน แล้วถ้าดันเก็บ Session ใน Container แล้วล่ะก็ ก็จะทำให้ Login หลุดนั่นเอง
  • ไม่มีการใช้บันทึกข้อมูลแบบ Persistent บน File System เหตุผลเดียวกันกับข้างบนก็คือ เพราะ Container มันถูกสร้างขึ้นหรือลบออกไปอยู่ได้ตลอดเวลา ดังนั้นไฟล์ที่อยู่บน Container จึงไม่มีความ Consistent แม้กระทั่งเอาไปใช้กับ Cache ในบางกรณีก็ยังอาจไม่เหมาะสม เพราะจะทำให้สามาถ Invalidaate Cache ได้นั่นเอง
  • อยากใช้ Cloud Functions แต่ต้องการ Timeout (ได้ถึง 15 นาที) และ Request Size ที่มากกว่า
  • เมื่อคำนวณแล้วว่า ปริมาณการ Request ไม่ได้เยอะตลอดเวลา แต่อาจจะมีการใช้งานสูงเป็นบางช่วง และต้องการรับโหลดให้ได้ การ Auto Scale บน Cloud Run เหมาะมากต้องการ
  • อยากใช้ Cloud Functions แต่ต้องการ Custom Domain แบบนี้ Cloud Run จัดให้ได้ แถม HTTPS ให้ด้วย ครบวงจรกันไปเลย เรียกว่าสามารถเอา Application เต็มๆ เนี่ยแหละไปโฮสได้เลย มันจุดการให้ทุกอย่าง

มาลองรัน PHP กับ Apache บน Cloud Run กัน

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

การจะใช้ Cloud Run ที่สำคัญที่สุดก็คือ Container ที่จะใช้รันนั่นเอง ซึ่ง Container ที่จะเอาไปใช้ได้นั้นโดยหลักแล้วก็แค่ทำให้มัน listen HTTP ไปที่ Port 8080 ได้ก็พอ แต่ทางที่ดีควรให้ listen ผ่านทางตัวแปร $PORT เลย (อ้างอิงจาก Container Runtime Contract)

เพื่อความแตกต่าง แทนที่เราจะมาทำท่าที่เหมือนกับที่ Tutorial บนเว็บมีให้ เดี๋ยวผมจะให้ GitLab เป็นตัวเก็บ Source Code และ Registry โดย Container ของ php:apache

เรามาเริ่มจากการเปิด GitLab Project ใหม่ แล้วเอา Source Code Hello World ไปใส่ แล้วก็เขียน gitlab-ci ให้มันสร้าง container ให้ ซึ่งจะได้หน้าตาประมาณนี้ ไปเปิด Repo ดูเองได้จากที่นี่เลย https://gitlab.com/spicydog/demo-cloud-run

ก่อนจะไปไกล ผมขอพาไปดู Dockerfile และ gitlab-ci ซะหน่อย ซึ่งตอนนี้มันมีหน้าตาประมาณนี้

Dockerfile

จะเห็นว่ามันไปโหลด Image ของ php:apache มา
แล้วเราก็ copy ไฟล์ทั้งหมดใน public ไปไว้ใน html
จากนั้นเซท default PORT เป็น 8080
ทีนี้เราจะต้อง เอาไว้ให้เปิด listen ตามตัวแปร $PORT ให้ทำการแก้ไข config ของ apache เดิม ให้จาก 80 เป็นอิงตามตัวแปร $PORT แทน แล้วก็ทำการรันตามปรกติ

.gitlab-ci.yml

ตัว gitlab-ci เราสั่งให้มัน build Dockerfile ของเรานั่นเอง ซึ่งก็จะใช้ dind (docker-in-docker image) สำหรับการ build เสร็จแล้วก็ให้เซฟลงไปที่ Registry ของ Project บน GitLab เลย

เข้าไปดู Container Registry ได้จากเมนูทางด้านซ้าย เปิดเข้ามาก็จะเห็นหน้าตาแบบนี้ ทีนี้เราก็ได้ container พร้อมสำหรับใช้งานแล้ว

NOTE: เนื่องจากผมเพิ่งมาเห็นว่า Cloud Run ใช้งานได้จาก Registry ของ GCP เท่านั้น ทีนี้ก็เลยต้องทำการย้ายไปโฮสบน Container Registry ของ GCP แทน ขออภัยไม่ได้เช็คตั้งแต่ต้น ซึ่งจริงๆ เราสามารถสั่งให้ GitLab CI ไปใช้ Registry ของ GCP แล้วให้มัน Auto Deploy ได้เลยซะด้วยซ้ำ แต่ปัญหาคือมันจะต้องใช้ Service Account Key ด้วย แล้วผมต้องการเปิด Repo นี้เป็น Public จึงไม่ค่อยจะเหมาะสมเท่าไหร่นั้น จึงขอทำการย้าย Registry แบบบ้านๆ ไปก่อนในตัวอย่างนี้

เอาน่ะ ไหนๆ ก็ไหนๆ มาย้าย Registry จาก GitLab ไปบน GCP กัน โดยใช้คำสั่งประมาณนี้

ทีนี้ก็มาเช็คดูที่ Container Registry บน GCP ก็จะเห็นแล้ว มี image โผล่ขึ้นมาแล้ว (ปรกติมันควรจะโผล่มาตรงนี้ตั้งแต่แรกอะนะ) จบแล้วสำหรับการเตรียม Container

ต่อมาเราเอา Container ไปรันบน Cloud Run กัน ไปที่ Google Cloud Platform Console แล้วก็ค้นหา Cloud Run แล้วก็เปิดเข้ามาเลย ถ้ายังไม่ได้ Enable API ก็ Enable ได้เลยครับ จากนั้นกด Create Service เราก็จะเห็นหน้าให้ Config ค่า ก็เอา URL ของ Container Registry มาใส่โลด

เอาจริงๆ ก็คือ ผมแค่เลือก Container image URL กับเช็คที่ Authentication ให้มันเข้าได้จากข้างนอกด้วย ที่เหลือมันใส่ให้หมดเลย กดไม่กี่คลิก ณ ตอนนี้เขียนนี่ยังมีให้ใช้งานเฉพาะโซน us คิดว่าอีกไม่นานคงจะมีให้ใช้ใน Region อื่นๆ ด้วย เมื่อพร้อมแล้วก็กด Create โลด!

หลังจาก Deploy ได้แล้วก็จะได้หน้าตาประมาณนี้ สังเกตดูที่ URL ข้างบน นั่นคืออันที่เข้าไปใช้งานได้เลย

จากนั้นก็ลองเปิดเข้าไปดู ของผมเข้าไปที่ไฟล์ info.php ที่จะโชว์ phpinfo();
(ลองไปเช็คดูที่ repo จะมีไฟล์นี้อยู่ใน directory public)

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


ทำ Custom Domain ให้ Cloud Run

กลับไปที่ Cloud Run แล้วจะเห็นปุ่ม Manage Custom Domains อยู่ข้างบน กดเข้าไป แล้วก็กด Add Mapping ก็จะเห็นอะไรประมาณนี้

กด Continue แล้วมันก็จะบอกว่า ให้ชี้ DNS ไปที่ไหน

ได้มาปั๊บก็เอาไปใส่ที่ DNS Server ที่เราใช้ สำหรับผมใช้ Cloudflare ก็จะหน้าตาประมาณนี้

เรียบร้อยแล้วก็กด Done แล้วก็มารอมันโหลดสักพัก มันจะทำการขอ HTTPS ผ่านทาง Let’s Encrypt ให้เราโดยอัตโนมัติ

พอเห็นว่ามันเป็นสีเขียวแล้วก็เปิดผ่านทาง domain ใหม่ได้เลย 😀

ได้ Certificate ของ Let’s Encrypt มาอย่างง่ายดาย เป็นแบบนี้แล้วก็ไม่ต้องมาวุ่นวายอะไรเลย หน้าที่ที่เหลือก็แค่ Deploy อย่างเดียว


น่าจะได้เห็นภาพการใช้งาน Cloud Run กันคร่าวๆ แล้วนะครับ สำหรับรอบนี้ต้องขออภัยที่เส้นทางที่ผมลองทำให้ดูค่อนข้างจะทุลักทุเลหน่อย เพราะทำท่ายากไปใช้ GitLab ทำ Registry ข้ามมา (ความจริงถ้าใครไปดู commit จะเห็นว่าทุลักทุเลกว่าอย่างที่เห็นอีก 555) แต่ใครอยากเห็นเส้นทางเรียบๆ สวยๆ ลองไปเปิดของ Google ดูได้เลย https://cloud.google.com/run/docs/quickstarts/build-and-deploy

ถึงแม้ตอนนี้ Cloud Run จะยังเป็น Beta อยู่ในตอนนี้ แต่ด้วย Concept ที่ค่อยจะน่าสนใจมาก คือมันมีอิสระสูงมากจริงๆ อยากจริงเราสามารถเอามาทำ Proxy อะไรต่างๆ ก็ได้หมดเลยนะ เพราะว่ามันรัน nginx ได้ทั้งก้อนเลย จากแต่ก่อนต้องมาเปิด Server ทิ้งไว้ ทีนี้จะจ่ายตามใช้งานจริง เหมาะสมมากสำหรับการเอามาทำ Prosonal Project ผมจึงมองว่านี่น่าจะเป็นบริการที่จะได้รับความนิยมมากในอนาคต ก็ลองศึกษากันเอาไว้ก่อนครับ น่าจะมีการปรับปรุงและพัฒนากันอีกพอสมควร ส่วนผู้อ่านท่านใดมีคำถาม หรือเห็นว่าเนื้อหามีส่วนผิดหรือคลาดเคลื่อนไปประการใดก็สามารถแจ้งผ่านทาง Comment ได้เลยนะครับ สำหรับตอนนี้ก็ขอจากกันแต่เพียงเท่านี้ สวัสดีครับ