เมื่อเราพัฒนาระบบมาถึงจุดนึง จะพบว่าเรามี service และ script มากมาย รวมถึง Function และ Class ที่เราเขียนไว้ใช้งานเอง เพื่อลดความซ้ำซ้อนของการทำงาน และบ่อยครั้งที่ function และ class ที่เราเขียนนั่นแหละ กลับซ้ำซ้อนมากขึ้นเสียเอง

เล่าเรื่องเวลาผ่านไป ทีมผมเองก็มีงานที่ทำเสร็จไปแล้วหลายโปรเจคท์ และหลายๆ โปรเจคท์ก็มี function เหมือนๆ กันไปหมด เพราะ reuse โดยการ copy-paste มาใช้ พอจะแก้มันซักทีนึงก็เลยต้องเสียเวลาแก้ซ้ำๆ กันไปทุกๆ โปรเจคท์

ทำยังไงดีนะ


รื้อฟื้นเรื่อง import ซักหน่อย

สมมติว่าเราเขียนโปรแกรมง่ายๆ มีไฟล์แค่เท่านี้

ที่ adder.py มี function หาผลรวมตัวเลข

ส่วนที่ main.py ก็แค่เรียกใช้ function เฉยๆ

ได้ผลลัพท์แบบไม่ต้องคิดมาก แบบนี้นะฮะ

แล้วถ้าเรามีสิบมีร้อยโปรเจคท์ ที่ต้องใช้ adder function เหมือนกัน เราจะ copy-paste มันรึเปล่า?


มารู้จักกับ Google Artifact Registry

Google Artifact Registry เป็น service ของ Google มีไว้เพื่อเก็บ docker image หรือ packge ในภาษา Python รวมถึง NodeJS และอีกหลายแบบ อ่านตัวเต็มได้ที่นี่เลยฮะ

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

  1. Build package แล้ว upload เข้า Google Artifact Registry repository
  2. เขียน setting เพื่อ access repo
  3. ติดตั้ง package
  4. Test ว่าเราสามารถ import package ได้ถูกต้อง

เริ่มกันเลย!

1. Build และ upload

(1) สร้าง repository ใน Google Artifact Registry

  • ก่อนอื่นเลย ก็ต้อง enable  API กันก่อนฮะ
  • การสร้าง repo ทำได้หลายวิธี จะทำผ่าน web console ก็ได้นะฮะ แต่เราเลือกทำผ่าน gcloud แบบนี้
gcloud artifacts repositories create {REPO-NAME} --repository-format=python --location={LOCATION}
  • เสร็จแล้ว ก็เช็คว่าทุกอย่างเรียบร้อย

(2) สร้าง package

นี่คือ library ที่เราต้องใช้ ให้ติดตั้งลงไปก่อนนะฮะ

Setup files ตามนี้ฮะ

  • LICENSE
  • README.md
  • pyproject.toml
  • src ควรอยู่ใน folder ให้ชื่อตรงกันกับ project name ใน pyproject.toml บรรทัดที่ 6 นะฮะ เพื่อให้ชื่อมันสื่อกัน
  • test files เว้นว่างไว้ก่อนก็ได้ฮะ ในตอนนี้

(3) Build package

python3 -m build

เมื่อผ่านไปด้วยดี เราจะเห็น folder dist อยู่ระดับเดียวกับ src นะฮะ

(4) Upload ขึ้นไป Google Artifact Registry

เมื่อทุกอย่างพร้อมแล้ว เราก็สามารถ upload ไปไว้ที่ repo ใน Google Artifact Registry

twine upload --repository-url https://{LOCATION}-python.pkg.dev/{PROJECT-ID}/{REPO-NAME}/ dist/*

(5) ตรวจสอบ package

  • Web UI
  • List packages
gcloud artifacts packages list --repository={REPO-NAME} --location={LOCATION}
  • List package versions
gcloud artifacts versions list --package={PACKAGE-NAME} --repository={REPO-NAME} --location={LOCATION}

2. Access repo

มาถึงตรงนี้ แปลว่า เราสำเร็จขั้นแรกที่สร้าง package แล้ว upload นะฮะ

จากนี้ คือเราเปลี่ยนฝั่งเป็นคนใช้งานบ้างละ แล้วจะต้องติดตั้ง package ตะกี้มาลงเครื่องเรา

จะต้องมี 3 สิ่งนี้

  1. .pypirc
  2. pip.conf
  3. requirements.txt ที่ระบุ index URLs

(1) Print setting จาก repo

Run คำสั่ง

gcloud artifacts print-settings python \
--project={PROJECT-ID} \
--repository={REPO-NAME} \
--location={LOCATION}

เราจะได้ output หน้าตาประมาณนี้มานะ

(2) Copy output ครึ่งบนไปไว้ที่ .pypirc

.pypirc จะเป็นแนวๆ นี้

(3) Copy อีกครึ่งไป pip.conf

แบบนี้ฮะ

(4) ใส่ package ไว้ที่ requirements.txt

-i หมายถึง flag --index-url เพื่อให้ pip มันรู้ว่าจะต้องไปหา package ที่ URL นี้ด้วยนะ

(5) Structure สุดท้าย

3. ติดตั้ง packages

ความเหนื่อยยากทั้งหมด จะเห็นผลก็ต่อเมื่อเราติดตั้งมันได้สำเร็จฮะ

pip install -r requirements.txt

และตรวจสอบว่าเครื่องเราติดตั้งได้มั้ย ด้วยคำสั่ง

pip list | grep {PACKAGE-NAME}

ถ้าเราลองไปดูที่ folder virtualenv จะเห็น package folder ของเราแบบนี้เลยฮะ

4. Test package

ยังไม่จบ ติดตั้งไปแล้ว ก็เรียกใช้งานว่าเราสามารถ import ได้มั้ย

แล้วก็จิ้ม run อย่างมั่นใจเลย

เนี่ย เสร็จจริงๆ ละ ภูมิใจมะ


Integrate กับ Docker image

เราจะพลาด Docker image ไปได้อย่างไรกันล่ะฮะ ต่อไปนี้คือการสร้าง Docker image ให้สามารถติดตั้ง package ของเราที่อยู่บน Google Artifact Registry ได้

1. เตรียม file

ควรมีอย่างน้อยตามนี้นะฮะ อย่าลืมว่าเราต้องใช้ .pypirc, pip.conf, และrequirements.txt ด้วยนะ

2. รู้จัก "OAuth 2.0 token" จาก GCP

ปกติเวลาเรา run คำสั่ง gcloud มันจะใช้ credential ที่เรา authenticate ไปตั้งแต่แรกนะฮะ  ไปเรียก GCP API แต่กับ Docker image มันไม่ใช่ เพราะข้างใน image คือพื้นที่โล่งๆ ซึ่งประเด็นหลักคือ เราจะ authenticate จากข้างใน image ได้ยังไง

คำตอบคือ "OAuth 2.0 Token"

ว่ากันรวบรัด "OAuth 2.0 token" คือ ข้อความ string ย้าวยาว ที่เอาไว้ใช้ authenticate กับระบบหนึ่งๆ ซึ่งของเราก็คือ Google Cloud Platform เนอะ รายละเอียดตามไปที่ลิงก์ด้านล่างได้เลยฮะ

Using OAuth 2.0 to Access Google APIs | Authorization | Google Developers

3. สร้าง OAuth 2.0 token

ทีนี้ เราก็จะต้องมาสร้างเจ้า OAuth 2.0 token กัน เพื่อให้เราสามารถ access และติดตั้ง package ใน requirements.txt ได้

ซึ่งหน้าตาของrequirements.txt ที่ถูกต้อง จะมีค่าของ OAuth 2.0 token แนวๆนี้นะ

ตรง token ya29.abc123 นั่นน่ะ เราจะได้มาจากการใช้คำสั่ง

gcloud auth print-access-token

ข้อมูลเพิ่มเติมของคำสั่งนี้ ไปตามอ่านได้ที่ลิงก์นี้เลยฮะ

อย่างนึงที่ควรจำ คือ ไม่ควรเก็บ credentials ไว้ที่ Git นะ

ทีนี้เราเลยจะสร้าง requirements.txt version มี OAuth 2.0 token จาก file เดิม โดยสร้างข้างใน image นั่นแหละ แล้วติดตั้ง package แล้วก็ลบอันที่มี token ทิ้งไปไงล่ะฮะ

4. เขียน Dockerfile

เอาล่ะ ได้เวลาเขียน Dockerfile กันแล้ว ตัวอย่างประมาณนี้นะฮะ

  • กำหนดให้รับค่า token โดยคำสั่ง ARG TOKEN ที่บรรทัดที่ 4
  • แปลงข้อความที่ https://{LOCATION}... ให้มีค่า token โดยการใช้ awk (ก่อนหน้านี้ พยายามใช้ sed แต่ไม่สำเร็จ เจอ error บ่อยจนเปลี่ยนวิธีฮะ)
  • แปลงเสร็จก็ save เป็นอีก file ตั้งชื่อมันว่า tokenized_requirements.txt
  • pip install จาก tokenized_requirements.txt
  • ลบ tokenized_requirements.txt ทิ้งไปเพราะข้างในมี credentials
  • ใช้ CMD เพื่อ run main.py เมื่อ container ถูกสั่งให้ทำงาน

5. Build image และ test run

สั่ง build ด้วยคำสั่ง

docker build \
--no-cache \
--progress=plain \
--build-arg TOKEN=$(gcloud auth print-access-token) \
-t entry-point:latest .
  • --no-cache คือ ล้าง cache ก่อน build image จะได้ไม่มีค่าอะไรก่อนหน้ามาก่อกวนฮะ
  • --progress=plain สั่งให้ print build progress ใน plain format
  • ตัวแปร TOKEN ถูก parse ค่าผ่าน flag ---build-arg
  • flag -t ตั้งชื่อ image นี้ว่า "entry-point"

สร้าง image เสร็จ ก็จิ้มคำสั่ง run ต่อได้เลย

docker run -it --name testpy entry-point

ถูกต้อง สมบูรณ์แบบที่สุดเลยฮะ


Diagram สรุป

Process ทั้งหมดทั้งมวลนั้น ผมทำเป็น diagram ประมาณนี้ เผื่อเข้าใจมากขึ้นฮะ

และแน่นอน source code ทั้งหมดเก็บไว้ที่ repo ข้างล่างนี้

GitHub - bluebirz/google-artifact-registry-custom-module: Private repo for custom modules
Private repo for custom modules. Contribute to bluebirz/google-artifact-registry-custom-module development by creating an account on GitHub.

Bonus track

แถมๆ ถ้าใครใช้ Google Cloud Composer อยู่แล้วอยากให้ Airflow มันใช้ package ของเราใน Google Artifact Registry ก็อ่านเพิ่มเติมตามลิงก์ข้างล่างนี้นะฮะ

Install Python dependencies for Cloud Composer | Google Cloud
Learn how to install PyPI packages for your Cloud Composer environment.

References