1 เดือนกับ Google Cloud Container Builder

Google Cloud แอบ launch Container Builder มาสักพักแล้วครับ (ยังไม่ขึ้นในหน้า product list) ก็เลยว่าจะลองเล่นสักหน่อย

Background

เดิมที TMStreamlabs อยู่บน GitLab ครับ และใช้ Docker ในการ deploy มาตั้งแต่ต้นอยู่แล้วเพราะบน Infra ใหม่ ทุกอย่างต้องเป็น Docker หมด

ของเดิมเลยผมจะใช้ GitLab CI มา build image และเก็บไว้บน GitLab Registry ซึ่งก็โอเคดียกเว้นว่า GitLab Registry pull ช้ามาก

ย้ายบ้าน

หลังๆ GitLab เริ่มไม่นิ่ง ผมก็เลยว่าได้เวลาย้ายบ้านแล้ว พอดีนึกออกว่า GitHub (Edu) ให้ private repo ไม่อั้นมาเป็นปีแล้ว ก็เลยย้ายไป GitHub พร้อมๆ กับย้าย builder มาใช้ Container Builder ซึ่งเค้าว่ามันเร็ว

สำหรับ Container Builder มีสมบัติดังนี้ครับ

  • (เค้าว่า) มันเร็ว เพราะใช้ network ของ Google
  • Builder รันบนเครื่อง n1-standard-1 ซึ่งได้ Dedicated CPU Core เลย ในขณะที่ GitLab ใช้ DigitalOcean ที่จะ share core ระหว่างผู้ใช้
  • ผมเดาว่า builder อยู่ในอเมริกา ซึ่งผมก็ส่ง feature request ไปแล้วว่าอยากให้เลือกที่อื่นได้
  • Parallel build step ได้ แต่ parallel build ทั้งหมดไม่ได้ยกเว้นข้าม project
  • ราคาไม่แพงเท่าไร และฟรีวันละ 120 นาทีซึ่งผมยังไม่เคยใช้หมด
  • โค้ดเราจะโดน mirror จาก GitHub มาใส่ Google Cloud Source Repositories

ซึ่ง build ของ TMStreamlabs ก็ไม่มีอะไรอยู่แล้วครับ ใช้ Dockerfile ได้เลย แล้วก็จะมี test step อีกทีนึง (เรารัน test หลัง build เพราะต้องการ test ด้วย container ผลลัพท์จริงๆ ซึ่งจะจับบั๊กบางประเภทได้ด้วย เช่นลืมลง shared library) วิธีการก็ไม่ยากครับ ใน cloudbuild.yaml ก็เขียนไปว่า

steps:
- name: gcr.io/cloud-builders/docker
  args: ['build', '-t', 'asia.gcr.io/$PROJECT_ID/tmstreamlabs', '.']
- name: gcr.io/cloud-builders/docker
  entrypoint: /workspace/scripts/test.sh
  env:
    - IMAGE_NAME=asia.gcr.io/$PROJECT_ID/tmstreamlabs
images:
  - asia.gcr.io/$PROJECT_ID/tmstreamlabs

Shared workspace

ความเจ๋ง (?) ของ Container Builder คือ folder ที่ build จะแชร์กันข้าม build step ครับ จะต่างกับ GitLab ตรงที่ GitLab จะต้องบอกให้มันก๊อปออกมาเอง (เพราะอาจจะรัน build step ที่เครื่องอื่น) ฉะนั้นแล้วมันทำให้เราเล่นอะไรได้สะดวกมากๆ

ตอนหลังผมเลยจัดชุดใหญ่เลยครับ ด้วยการเพิ่ม Gulp + Webpack เข้ามาใน project Django ซึ่งเดิมทีผมตั้งคำถามหลายรอบว่าจะรัน build step แบบนี้ตรงไหนดี ก็พบว่า pattern แบบนี้แหละเวิร์คที่สุดแล้ว

steps:
- name: yarnpkg/node-yarn
  args: ['./scripts/minify.sh']
- name: gcr.io/cloud-builders/docker
  args: ['build', '-t', 'asia.gcr.io/$PROJECT_ID/tmstreamlabs', '.']
- name: gcr.io/cloud-builders/docker
  entrypoint: /workspace/scripts/test.sh
  env:
    - IMAGE_NAME=asia.gcr.io/$PROJECT_ID/tmstreamlabs
images:
  - asia.gcr.io/$PROJECT_ID/tmstreamlabs

สังเกตว่าเราจะเพิ่มอีก step ไปก่อนหน้าครับ โดยจะรัน minify.sh ซึ่งข้างในก็จะรัน yarn install + gulp แล้วก็ลบ node_modules ทิ้งไป

สำหรับในฝั่ง Django จะเซตใน settings.py ดังนี้ครับ

built_static_root = os.path.join(BASE_DIR, 'static')
if os.path.isdir(built_static_root):
    STATICFILES_DIRS = [built_static_root]

ซึ่ง script Gulp ของผมนั้นจะไป scan หา /static/js/ แล้ววิ่งผ่าน uglifyjs เข้าไปเก็บที่ static/js/* ด้านนอก ทำให้ตอน dev ก็จะเรียกไฟล์ไม่ minify เวลา compile แล้วก็จะเรียกไฟล์ minify ได้เลย และวิ่งผ่าน collectstatic ได้ด้วยทำให้สามารถใช้ Manifest static file storage ได้ (สำหรับไฟล์ที่ใช้ webpack จะเก็บแยกนอก static ครับ ซึ่งเวลา dev ต้องเปิด gulp watch ค้างไว้ให้มัน build ตลอด)

Parallel Step

ตอนแรกๆ Parallel step มีบั๊กครับ คือ schema จะ validate ไม่ผ่านเลย แต่ตอนนี้เหมือนจะแก้แล้ว ทำให้เราสามารถรัน step หลายๆ อันพร้อมกันได้ เช่นแบบนี้

steps:
- name: yarnpkg/node-yarn
  args: ['./scripts/minify.sh']
- name: gcr.io/cloud-builders/gsutil
  args: ['cp', '-r', 'gs://tmsbuildassets/', '/workspace/gs/']
  waitFor: ['-']
- name: gcr.io/cloud-builders/docker
  args: ['build', '-t', 'asia.gcr.io/$PROJECT_ID/tmstreamlabs', '.']

# prepull images in use
- name: gcr.io/cloud-builders/docker
  args: ['pull', 'mariadb:latest']
  waitFor: ['-']
- name: gcr.io/cloud-builders/docker
  args: ['pull', 'redis:alpine']
  waitFor: ['-']
- name: gcr.io/cloud-builders/docker
  args: ['pull', 'willwill/wait-for-it']
  waitFor: ['-']

- name: gcr.io/cloud-builders/docker
  entrypoint: /workspace/scripts/test.sh
  env:
    - IMAGE_NAME=asia.gcr.io/$PROJECT_ID/tmstreamlabs
images:
  - asia.gcr.io/$PROJECT_ID/tmstreamlabs

นี่คือทั้งไฟล์ที่ใช้อยู่แล้วครับ ซึ่งจะเห็นว่ามันจะ pull images ทั้งหมดมาพร้อมกันเลย และพอเราบอกว่ามันไม่ depends on อะไรเลยจะทำให้มัน pull พร้อมๆ กับรัน yarn เลย

อีกอันนึงที่จะเห็นนะครับ คือผมใช้ gsutil ใน build script ด้วย ซึ่งเจ้าเครื่อง container builder นี่จะมีสิทธิ์พิเศษใน IAM (จะเขียนว่า Google APIs service account อีเมล projectid@cloudbuild.gserviceaccount.com) ทำให้เราสามารถ grant สิทธิ์ให้เครื่อง builder โดยเฉพาะได้ ไม่ต้องเปิด public

ข้อเสีย

สำหรับตอนนี้ข้อเสียที่เจอคือ error reporting มันไม่ดีเลยครับ เพราะ build เสร็จมันจะเงียบไปเลย ในขณะที่ GitLab CI จะเมลสถานะกลับมา ตรงนี้อาจจะต้องเอา image อีกอันมา trigger service แจ้งเตือนอีกที ซึ่งก็ไม่รู้ว่ามันจะทำให้ระบบไม่หยุดทำงานตอนเจอ error ไปซะก่อนได้หรือเปล่า