Under the Cloud – GCP

ในฝั่ง Google แล้ว เนื่องจากองค์กรโตมาจากสาย research รายละเอียดการ implement ของ GCP จึงไปอยู่ใน Paper แทน โดย Paper ที่สำคัญคือ “Large-scale cluster management at Google with Borg” ซึ่งออกมาในปี 2015

Borg

ถ้าใครเคยอ่านประวัติของ Kubernetes จะทราบว่า Kubernetes เป็นความพยายามออกแบบระบบ cluster management ของ Google ขึ้นมาใหม่เป็น open source โดยระบบเดิมที่ Google ใช้อยู่นั้นชื่อ Borg

แน่นอนว่า Google จึงไม่ได้ใช้ Kubernetes แต่ยังใช้ Borg อยู่

ในส่วนการทำงานของ Borg จาก Paper จริงๆ ก็แทบจะเหมือน Kubernetes ถ้าเข้าใจส่วนประกอบของ Kubernetes ดีแล้วก็คงไม่จำเป็นจะต้องอธิบายเพิ่ม

Borg high level architecture

จากภาพด้านบน ถ้าเปรียบเทียบกับ Kubernetes แล้ว BorgMaster ก็คือ Kubernetes master, Paxos store ก็คือ etcd, Borglet คือ Kubelet, Cell คือ Cluster และ borgcfg คือไฟล์ YAML นั่นเอง

ในแต่ละงานที่รันใน Borg จะต้องระบุ Quota ที่ต้องการใช้ ซึ่งมีความซับซ้อนกว่า Kubernetes resource มาก เพราะระบุได้ทั้งปริมาณ CPU, RAM, Disk, ฯลฯ ที่ต้องการใช้, ลำดับความสำคัญ และเวลาที่ต้องการใช้ โดยผู้ใช้งาน Borg จะต้องซื้อ Quota ไว้ก่อนใช้งาน

พอทราบ Resource ของ Workload ที่ต้องการใช้แล้ว Borg scheduler ก็จะจัดหา Borglet ที่จะรัน Workload นั้นให้อัตโนมัติ เช่นเดียวกับ Kubernetes

ในปี 2018 มีคน Hack Google Sites ผ่านช่องโหว่ใน Google Caja ได้ และดูดหน้า admin Borg รวมถึงรายละเอียดต่างๆ ออกมาเล่าให้ฟังได้เล็กน้อย ตามบทความ Into the Borg และมีคนแกะ Borg protobuf มาจาก App Engine อีกด้วยใน $36k App Engine RCE

App Engine

เขียนมาอย่างนานอาจจะสงสัยว่าทำไมจะต้องเล่าถึง Borg ด้วย?

คำตอบคือ Google เอา Workload ของเราไปรันบน Borg ครับ! บนเครื่องเดียวกับที่เรารัน โค้ดของเรานั้นอาจจะมี Gmail หรือ YouTube Encoder รันอยู่พร้อมๆ กันก็ได้ ซึ่งใน Paper ก็ได้ยึนยันไว้ในหัวข้อ 6.1 แล้วว่า

VMs and security sandboxing techniques are used to run external software by Google’s AppEngine (GAE) and Google Compute Engine (GCE). We run each hosted VM in a KVM process that runs as a Borg task.

Product แรกของ Google Cloud คือ App Engine ที่เปิดตัวมานานกว่า 10 ปีแล้ว ถ้าใครเคยเขียน App Engine โดยใช้ runtime ตัวแรกก็จะทราบว่ามีข้อจำกัดพอสมควร เช่น

  • ห้ามใช้ Native module
  • ห้าม Write ลง Disk
  • ห้ามต่อออก Network
  • ห้าม import บาง module

ผมไม่แน่ใจว่า App Engine ใช้อะไรมา Sandbox Application ของเรา แต่เข้าใจว่าเนื่องจากข้อจำกัดการใช้งานต่างๆ แล้วจึงไม่ได้ใช้ Sandbox อื่นๆ ครอบทับไปอีก ใน Paper ของ Borg เองก็ระบุว่าใช้เพียง chroot และ cgroup เท่านั้น

ในปี 2018 Google ได้เปิดตัว App Engine Second Generation Runtime ซึ่งใช้ gVisor เป็นระบบ Sandbox แทน ทำให้สามารถรันทุกคำสั่งได้ตามปกติ

gVisor

เนื่องจาก Linux เป็น OS เดียวที่การันตีว่า system call table จะ Stable จึงมีหลายคนที่พยายาม implement system call table ของ Linux ขึ้นมาใหม่

คนหนึ่งที่เราอาจจะรู้จักกันดีคือ Windows Subsystem for Linux ซึ่ง map Linux system calls ไปยัง NT kernel system calls เช่นเดียวกัน gVisor ก็เป็นการเขียน Linux system calls ขึ้นมาใหม่ในภาษา Go

gVisor

การที่ gVisor เขียนด้วยภาษา Go นั้นทำให้โค้ดมีความปลอดภัยกว่าภาษา C ที่ใช้เขียน Linux kernel เนื่องจาก runtime ของภาษาจะจัดการ Memory ให้โดยอัตโนมัติ นอกจากนี้เนื่องจากมันแค่ implement system calls อย่างเดียว ทำให้มันไม่กินทรัพยากรมากเท่ากับ Virtualization

Compute Engine

อย่างที่เล่าไปในหัวข้อด้านบน การสร้าง Compute Engine เครื่องหนึ่งก็คือการสร้าง Borg task หนึ่งที่รันอิมเมจของเราบนโปรแกรม KVM

ประเด็นหนึ่งที่มีคนเคยถามผมคือ Live migration ของ Google Cloud ทำงานอย่างไร? Google ได้เล่าไว้แล้วในบทความ Live migration

สิ่งที่เกิดขึ้นตอน Live migration คือ

  1. มีสัญญาณมาให้ Live migrate เช่นมีการ maintenance hardware หรือระบบตรวจพบว่า hardware ใกล้เสียแล้ว
  2. Scheduler จะจัดหาเครื่องใหม่มาให้
  3. เมื่อได้เครื่องใหม่แล้ว เครื่องใหม่จะเปิด VM เปล่าๆ ขึ้นมา
  4. เครื่องเดิมจะ Dump Memory ของ VM ส่งไปให้เครื่องใหม่
  5. กระบวนการข้อ 4 เนื่องจากจะใช้เวลาส่งข้อมูลพอสมควร จึงจะต้อง Track ด้วยว่ามีการ Update memory ตรงไหนบ้างระหว่างการทำงานนี้ (Delta)
  6. หลังจากส่ง Memory ชุดแรกเสร็จแล้วก็จะส่ง Delta ตามไปด้วย
  7. วนซ้ำข้อ 6 ไปเรื่อยๆ จนระบบคิดว่าการส่ง Update นั้นไม่คุ้มค่าที่จะทำแล้ว
  8. VM จะถูก Pause และระบบจะส่ง Delta ชุดสุดท้าย
  9. เครื่องใหม่เมื่อมี state ครบแล้วก็จะทำงานต่อ
  10. เครื่องเดิมแม้จะหยุดทำงาน VM แล้ว แต่ยังจะต้อง forward network packet ที่ส่งเข้ามาไปยังเครื่องใหม่เรื่อยๆ จนกว่าระบบ network จะ update แล้ว

ถ้าเคยเล่นเกม Emulator กระบวนการนี้ก็คล้ายๆ กับการกด Save state ในเกมแล้ว Load state เมื่อต้องการเล่นต่อ เพียงแต่เกิดขึ้นเป็น Realtime

Kubernetes Engine

สำหรับคนที่เคยเล่น Kubernetes Engine จะเห็นว่า Master ไม่ปรากฏเป็น Node ใน Cluster และโปรแกรมต่างๆ ที่รันอยู่ใน Master เช่น kube-apiserver, kube-scheduler, ingress-gce นั้นจะไม่ปรากฏเป็น Pod เหมือนกับ Cluster ที่ติดตั้งเอง

แล้ว Master อยู่ที่ไหน?

ใน Borg Paper มีย่อหน้าหนึ่งกล่าวไว้ว่า

Google’s open-source Kubernetes system places applications in Docker containers onto multiple host nodes. It runs both on bare metal (like Borg) and on various cloud hosting providers, such as Google Compute Engine.

จากด้านบนทำให้ผมเชื่อว่า GKE Master นั้นรันอยู่บน Borg โดยไม่ใช้ VM แต่ Docs ของ Kubernetes Engine กลับบอกไว้อีกแบบหนึ่ง

Every cluster has a Kubernetes API server called the master. Masters run on VMs in Google-owned projects. In a private cluster, you can control access to the master.

Private Clusters

ก็อาจจะเป็นไปได้ว่าเฉพาะถ้าเราเปิด Private Cluster เท่านั้น Master จึงจะรันอยู่ใน VM แล้วทำ VPC Peering เข้ามายัง project ของเรา

แต่ข้อมูลที่ถูกต้องที่สุดว่า Master รันอยู่ที่ไหน ตอนนี้ก็ยังไม่มีเปิดเผยออกมา


โดยสรุปแล้ว ผมคิดว่า Cloud ของ Google ออกแบบมาได้เฉพาะตัวมากๆ และหาคนเลียนแบบได้ยาก เนื่องจากว่าระบบสามารถเอา workload ของ Google (ที่ไม่ต้องใช้ VM) ปะปนกับ Workload ของลูกค้าได้ ทำให้ทำราคาได้ถูกมาก

ผมลองเปรียบเทียบเครื่อง n1-standard-2 + 25GB Standard Disk ใน region us-central-1 ที่ราคาถูกเกือบที่สุด กับเครื่อง General Purpose ขนาดเล็กสุดของ DigitalOcean (ซึ่งไม่มีการ share CPU กับลูกค้าอื่น) นั้นก็พบว่าราคาของ Google ถูกกว่าถึง $10/เดือน

(ทั้งนี้ราคา Data Transfer ของ Google Cloud นั้นน่าจะสูงที่สุดในบรรดา Cloud provider แล้ว เพราะใช้ routing แบบพิเศษ)

แต่ด้วยการ design ระบบแบบนี้ เราจึงไม่น่าจะเห็นวันที่ Google ย้ายไปเป็น hardware ล้วนๆ เหมือน AWS เพราะ software ที่รันอยู่ด้านบนนั้นมีหน้าที่มากมายหลายอย่าง (นี่ผมยังไม่ได้เล่าถึงฝั่ง Networking เลย…)


ในตอนต่อไปจะกลับไปเล่าถึงฝั่ง AWS บ้าง ว่า Service อื่นๆ นอกจาก Compute นั้น เบื้องหลังทำงานอย่างไร?

ลองเล่น Stackdriver Trace บน Django

หลังจาก implement AWS X-Ray ให้ที่บริษัทไป ผมก็เลยว่าอยากจะ implement ระบบคล้ายๆ กันให้ TipMe บ้าง แต่ TipMe อยู่บน Google Cloud ก็เลยจะต้องใช้บริการของเค้าคือ Stackdriver Trace

(Note: ถ้ายังไม่ได้อ่านตอนของ X-Ray แนะนำให้อ่านก่อนเพื่อเข้าใจ concept ของ Tracing ครับ)

Zipkin

สำหรับการใช้งาน Stackdriver Trace นั้นจะแตกต่างกับของ X-Ray ตรงที่ Trace มี API ให้เราเลือกใช้ถึง 3 แบบ คือ

  1. REST API ซึ่งจะเป็น API เฉพาะของ Trace เอง เช่นเดียวกับของ X-Ray
  2. gRPC API เป็น custom API เช่นกัน แต่ตอนนี้ยังไม่มี client library ให้ใช้
  3. Zipkin API ซึ่ง Zipkin เป็นโปรแกรมสำหรับทำ tracing แบบ open source แล้ว Google Cloud นำมาแก้ให้ใช้ gRPC API ของเค้าเป็น backend อีกทีหนึ่ง (จะเรียกว่าเป็น adapter ก็ได้)

เพื่อไม่ให้ vendor lock in เราก็จะเลือกใช้ Zipkin API ฉะนั้นอย่างแรกที่จะต้องทำคือติดตั้ง Zipkin Collector ของเค้าก่อน สำหรับบน Kubernetes ก็ใช้ Deployment ประมาณนี้

apiVersion: v1
kind: ReplicationController
metadata:
  name: zipkin
spec:
  replicas: 1
  selector:
    app: zipkin
  template:
    metadata:
      labels:
        app: zipkin
    spec:
      containers:
      - name: zipkin
        image: gcr.io/stackdriver-trace-docker/zipkin-collector
        ports:
        - containerPort: 9411
        env:
        - name: GOOGLE_APPLICATION_CREDENTIALS
          value: /secrets/service-account
        livenessProbe:
          httpGet:
            path: /health
            port: 9411
          initialDelaySeconds: 120
        readinessProbe:
          httpGet:
            path: /health
            port: 9411
        resources:
          requests:
            memory: "0Mi"
            cpu: "3m"
          limits:
            memory: "256Mi"
        volumeMounts:
        - name: secret
          mountPath: /secrets
          readOnly: true
      volumes:
      - name: secret
        secret:
          secretName: zipkin
---
apiVersion: v1
kind: Service
metadata:
  name: zipkin
  labels:
    app: zipkin
spec:
  selector:
    app: zipkin
  ports:
    - port: 9411
---
apiVersion: v1
kind: Secret
metadata:
  name: zipkin
type: Opaque
data:
  service-account: แปะ service account เป็น base64

เนื่องจากบน Kubernetes เราจะใช้ instance role ไม่ได้ (เพราะไม่ได้เซตสิทธิ์ไว้ตอนเปิด cluster) ก็เลยจะต้องใช้ Service account แทน วิธีการสร้าง Service account ก็คือ

  1. เข้าไปที่ Service Accounts
  2. กด Create service account ด้านบน
  3. กรอก Service account name ตามชอบ และเลือก Role เป็น Cloud Trace > Cloud Trace Agent
  4. ติ๊ก Furnish a new private key และเลือก JSON
  5. กด Create
  6. เมื่อได้ JSON มา ให้เอาไปเข้ารหัส Base64 แล้วแปะใน Secret ด้านบน (cat file.json | base64 -w0)

พอเสร็จแล้วก็ kubectl apply -f zipkin.yaml เป็นอันเรียบร้อย

Integrate กับ Django

ถัดมาเราจะต้องติดตั้ง Tracer ลงในโปรแกรมของเรา ซึ่งถ้าเป็น Node.js แบบที่วงในก็คงจะง่าย แต่พอเป็น Python แล้วก็พบว่าเราคงจะต้องทำเอง -_-!! ซึ่งของที่ผมออกแรงนั่งทำมาก็ปล่อยเป็น open source แล้วครับ ฟีเจอร์หลักๆ ก็คือ

  • Trace request ได้
  • Trace request ออกไปทาง urllib3 (request module) ได้ รวมทั้งจะแปะ X-B3-TraceId ส่งไปให้ด้วย
  • Trace template render time ได้
  • Trace database query ได้ (แต่จะไม่ log parameter ของ query เพื่อความปลอดภัย)

วิธีการติดตั้งก็คือ

  1. เพิ่ม django-zipkin-trace==1.0.0 ลงใน requirements.txt
  2. pip install -r requirements.txt
  3. แก้ไข settings.py ของเราในส่วนของ MIDDLEWARES เติม zipkin_trace.ZipkinMiddleware ไปเป็นอันบนสุด
  4. เพิ่ม ZIPKIN_SERVER='http://zipkin:9411' ลงใน settings.py

ถ้าอยากลองเทส ก็สามารถเทสบนเครื่องได้โดยใช้ Zipkin ของแท้ ดังนี้

  1. Start Zipkin ด้วยคำสั่ง sudo docker run -d -p 9411:9411 openzipkin/zipkin
  2. เช็คว่าสามารถเข้า Zipkin ได้ที่ http://localhost:9411 (ใครใช้ Docker for Mac/Windows อาจจะต้องเข้า IP ของ VM แทน)
  3. แก้ไข ZIPKIN_SERVERS ใน settings.py ให้ชี้ไปที่ http://localhost:9411 แทน
  4. ลองเข้าเว็บ Django ของเราแล้วรีเฟรชสองสามที
  5. ใน Zipkin กด Find Traces น่าจะปรากฏ Trace ขึ้นมา

(Note: py-zipkin จะสะสม trace ให้ครบ 100 อันก่อนส่ง ต้องเปิด DEBUG=true ถึงจะส่ง trace ทันที)

Trace ที่ได้จะหน้าตาประมาณนี้

สำหรับถ้าจะใช้บน Google Cloud Trace จะหน้าตาแบบนี้

เปรียบเทียบกับ X-Ray

จากที่ใช้งานมาทั้ง 2 platform แล้ว ผมพบว่าสิ่งที่ X-Ray จะนำเสนอคือความสัมพันธ์ระหว่าง service และ error ระหว่างทำงานมากกว่า เพราะมุมมองการดู Trace นั้นสามารถใช้งานได้สะดวกมาก และมีแผนภาพระหว่าง service ด้วย

สำหรับทางฝั่ง Stackdriver Trace นั้น จุดขายคือ Auto analysis ครับ

พอเราใช้ไปสักสัปดาห์นึงแล้ว URL ไหนที่ hit บ่อยที่สุด top 3 จะถูกนำมาเทียบกับสัปดาห์ที่แล้วโดยอัตโนมัติ เพื่อให้เราเห็นว่าที่เราทำไปในสัปดาห์นี้นั้นทำให้เว็บเร็วขึ้นหรือช้าลงหรือเปล่า และสามารถกดดูตัวอย่าง Trace ใน percentile ต่างๆ ได้ด้วย (ถ้าใช้ X-Ray จะต้องไปลากในกราฟเอาเอง)

โดยส่วนตัวผมชอบแบบ X-Ray มากกว่า เพราะอยากรู้เรื่อง error เป็นหลักมากกว่าซึ่งใน Trace มันดูไม่ได้ (เข้าใจว่าน่าจะให้ไปใช้ Stackdriver Error Reporting แทน แต่ผมใช้ Sentry อยู่แล้ว)

สุดท้าย Stackdriver Trace ฟรี 100% ครับ ไม่มีข้อจำกัดใดๆ ทั้งสิ้น ฉะนั้นที่ TipMe ตอนนี้ก็จะยิงทุก request เข้าหมดทุกอันได้เลย