วิธี run containers บน Azure Container Apps แบบรองรับหลาย port
ปัญหาที่เกิดขึ้น
ใน project ที่ทำอยู่นั้นจะต้องนำ application ที่อยู่ในรูปแบบ container ไป run บน Azure Container Apps (ACA) ซึ่งรองรับการ run หลาย ๆ container พร้อมกันใน application เดียวกัน ทีมเราจึงวางแผนที่จะ run application บนหลาย ๆ port ยกตัวอย่างเช่น HTTP stub server ยี่ห้อต่าง ๆ อย่าง mountebank หรือ Smocker ที่จะต้องมีอย่างน้อย 2 port ขึ้นไปได้แก่
- Port ที่เอาไว้สร้าง/แก้/ลบข้อมูล (configure) ของ stub เช่น path, port, request, response (ต่อไปนี้จะขอเรียกว่า management port)
- Port ที่ใช้งาน stub ตามที่กำหนดไว้ข้อแรก (ต่อไปนี้จะขอเรียกว่า stub port)
แผนดูสวยงามนะแต่ปัญหาคือว่า ณ ขณะที่เขียนบทความอยู่ทาง Microsoft ยังไม่รองรับการเปิดเกิน 1 port ตาม GitHub issues นี้เลย
ดังนั้นในระหว่างที่ต้องรอนี้เราต้องหาวิธีแก้ไปพลาง ๆ ก่อน ซึ่งวิธีที่เราใช้คือเอา reverse proxy เข้ามาช่วย
แก้ด้วย reverse proxy
Reverse proxy คือตัว server นี่เชื่อมต่อกับ internet ด้านนอกที่ client จะส่ง request เข้ามา จากนั้นก็จะผ่าน request เข้าไปที่ server ปลายทางที่ไม่ได้เปิดออกสู่ internet ด้านนอกด้วยตัวเอง บางตัวนั้นมันสามารถอ่าน path หรือ header หรืิอ cookie ได้ ซึ่งเป็นสิ่งที่เราต้องการเนื่องจาก path ของ stub server นั้นมันเปลี่ยนไปตามที่เรา configure เลย
ในบทความนี้เราจะใช้ NGINX และ mountebank เป็นตัวอย่าง อย่างที่บอกไปหัวข้อที่แล้วว่าเรา run application บน container ด้วย Docker ดังนั้นพอเรามาเขียน Dockerfile ก็จะได้รูปร่างหน้าตาประมาณนี้
ในส่วนของ mountebank ก็ใช้ base image ตรง ๆ ได้เลย
จะสังเกตว่าความต่างจาก NGINX base image คือแค่เราต้องแทน configuration ดั้งเดิมด้วยของใหม่ของเรา ซึ่งนี่คือจุดที่เราจะต้องมาทำต่อเพื่อให้ reverse proxy ของเราส่ง request ไปหา stub server ให้ถูก port นั่นเอง ก็จะได้หน้าตาประมาณนี้
คำอธิบาย
- เรา configure
worker_connections
แบบ global ผ่านevents
block เพื่อรองรับ 4096 connection พร้อมกันได้ใน 1 process (จะมีหรือไม่มีก็ได้แล้วแต่ scale) - ในส่วนของ configuration ที่เกี่ยวกับ reverse proxy โดยเฉพาะก็จะอยู่ใน
http
->server
block เป็นส่วนใหญ่- ให้ตัว NGINX เองเปิด port ไว้รับ request ที่ port
80
ที่ domainlocalhost
เนื่องจากเรา deploy reverse proxy ไว้กับตัว stub server ใน ACA เดียวกัน เลยสามารถเชื่อมกันผ่านlocalhost
ได้เลย - ถ้า client อยากจะเชื่อมกับ stub server บน management port (ตัวอย่างคือ port
2525
) ก็ให้ส่งมาที่ path ที่ขึ้นต้นด้วย/management
ทีนี้เราจะทำการrewrite
เพื่อดึง path ส่วนที่อยู่หลัง/management
มาใช้ในการผ่าน request ไปที่ management port (ตัวอย่างเช่น ถ้า client จะส่ง request ไปที่ stub server ด้วย path/imposters
ก็จะต้องใช้ path/management/imposters
เพื่อส่งไปยัง NGINX แทนเพื่อที่ NGINX จะได้ส่ง request ต่อไปที่http://localhost:2525/imposters
นั่นเอง) - ถ้า client อยากจะเชื่อมกับ stub server บน stub port ที่เลข port จะเป็น dynamic แต่ที่แน่ ๆ มันเป็นตัวเลขดังนั้นเราสามารถ configure NGINX ให้ใช้ path ที่ขั้นต้นด้วย
/stub
ตามด้วยตัวเลข port ของ stub นั้น ๆ โดย regular expression (regex) จะเป็น/stub(\d+)(.*)
ในส่วนของ~*
ก็คือเวลา check regex จะเป็น case-insensitive ถ้าอยากได้เป็น case-senstive ก็จะเป็น~
แทน - ส่ง request ไปหา stub server ที่
http://localhost:<stub_port>
โดยที่stub_port
นั้นก็คือส่วนของเลข port หลัง/stub
ตัวอย่างเช่นเรา configure stub ไว้ที่ port1234
ก็จะต้องใช้ path ขึ้นต้นด้วย/stub1234
เพื่อให้ NGINX ไปดึงเลข1234
ผ่าน$1
(วงเล็บที่ 1 ของ regex pattern) - ส่วนที่เหลือก็คือทำการ
rewrite
เพื่อดึง path ส่วนที่อยู่หลัง/stub<stub_port>
มาใช้ในการผ่าน request ไปที่ stub port ผ่าน$2
(ตัวอย่างเช่น ถ้า client จะส่ง request ไปที่ stub server ด้วย path/customers/1a2b
ที่ port1234
ก็จะต้องใช้ path/stub1234/customers/1a2b
เพื่อส่งไปยัง NGINX แทนเพื่อที่ NGINX จะได้ส่ง request ต่อไปที่http://localhost:1234/customers/1a2b
นั่นเอง) - เนื่องจาก port มันเป็น dynamic ใน configuration ของเรา stub server เลยต้องใช้ตัวแปรชื่อ
mountebank_stub_url
แทน ปัญหาคือถ้าเป็นตัวแปร NGINX จะให้เรากำหนด DNS server เพื่อให้ resolver มาใช้ในการแปลงlocalhost
ไปเป็น IP ของ Docker ดังนั้นเราจึงต้องใส่http
->resolver
block เป็น DNS server ของ Docker ซึ่งก็คือ127.0.0.11
- ให้ตัว NGINX เองเปิด port ไว้รับ request ที่ port
ผลลัพธ์
เพื่อพิสูจน์ว่าใช้ได้จริงเราก็เอาไป deploy บน ACA แล้วก็ส่ง request เข้าไปผ่าน ACA ingress URL ดูผ่าน path /management
ก็จะได้ประมาณนี้
วิธีแก้นี้น่าจะพอใช้ไปได้พลาง ๆ แต่ทั้งนี้ทั้งนั้นถ้าทาง Microsoft เค้ารองรับให้ใช้ port หลาย ๆ อันได้ก็จะเปลี่ยนไปใช้ของเค้าดีกว่าเพราะ configuration แบบนี้ก็ซับซ้อนและต้องศึกษาการใช้งาน NGINX ในระดับนึงเลย