Python package ของเราก็เก็บไว้ที่ repo ของเราเอง
เมื่อเราพัฒนาระบบมาถึงจุดนึง จะพบว่าเรามี 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 ของเรากัน จะได้มีที่เดียว จะต้องแยกๆ ให้ปวดหัว และนี่คือสิ่งที่เราต้องทำฮะ
- Build package แล้ว upload เข้า Google Artifact Registry repository
- เขียน setting เพื่อ access repo
- ติดตั้ง package
- 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 สิ่งนี้
.pypirc
pip.conf
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 เนอะ รายละเอียดตามไปที่ลิงก์ด้านล่างได้เลยฮะ
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
เพื่อ runmain.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 ข้างล่างนี้
Bonus track
แถมๆ ถ้าใครใช้ Google Cloud Composer อยู่แล้วอยากให้ Airflow มันใช้ package ของเราใน Google Artifact Registry ก็อ่านเพิ่มเติมตามลิงก์ข้างล่างนี้นะฮะ
References
- Python packaging
https://packaging.python.org/en/latest/tutorials/packaging-projects/ - Publish with twine
https://www.geeksforgeeks.org/how-to-publish-python-package-at-pypi-using-twine-module/ - Google Artifact Registry official docs
https://cloud.google.com/artifact-registry/docs/python/authentication - Python package in Google Artifact Registry on Medium
https://lukwam.medium.com/python-packages-in-artifact-registry-d2f63643d2b7 - String substitution not using
sed
https://unix.stackexchange.com/questions/97582/how-to-find-and-replace-string-without-use-command-sed