โอชิเมม Keyakizaka46: September Edition

ผ่านไปเดือนเดียวไม่คิดว่าโอชิจะปรับไวมาก คือรู้สึกว่าเคยาคิเตะนี่มันจะมีเฟสที่บางคนจะเด่น หมดเฟสนั้นไปคนอื่นก็จะมาแทน อย่างตอนนี้ก็น่าจะเป็นเฟสของนากาซาว่าคุง บางคนที่เคยออกมาบ้างอย่างโยเนซังตอนนี้ก็ออกมาน้อยลงมากๆ ซึ่งมันส่งผลต่อลิสต์มากอย่างน่าแปลกใจ

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

ว่าแล้วก็มาลองปรับลำดับใหม่ดูสักหน่อย

Note: ชื่อในวงเล็บคือชื่อที่เราเรียก ซึ่งบางทีชาวบ้านก็ไม่ได้เรียกกันแบบนี้

คามิโอชิ

วาตานาเบะ ริสะ

ริสะหลังๆ มาเริ่มแต่งหน้าจัดขึ้น คงเพราะเป็นนางแบบ ส่วนตัวเราไม่ชอบเท่าไร คือชอบริสะที่มองดูเด็ก (และทำตัวเด็ก) แบบในภาพมากกว่านะ

คิดถึงริสะแบบ Sๆ ด้วย ดูตอนที่ไปเที่ยวกับโมนะแล้วก็พอได้ แต่รู้สึกว่าแต่ก่อน S กว่านี้

โคบายาชิ ยุย (ยุยปง)

ยินดีต้อนรับคามิโอชิใหม่เรา ยุยปง!!

ตอนที่รู้ตัวว่าชอบยุยปงคือไปดูรวมคลิปสักอัน (รู้สึกว่าจะเป็นของมงตะ) แล้วเห็นชอทที่ปงคุยกับริสะ ก็เออปงก็น่ารักดี แล้วหลังๆ มาจะเห็นว่าปงหน้าบานขึ้น ดูสาวมากกว่ารูปใน Fukyouwaon ที่โคตรแมน

เคยาคิเตะ SP ยุยปงได้ร้องสดคนเดียวด้วย เสียงใช้ได้เลย ถึงจะ hit note ไม่หมดก็ตาม

โอชิ

จะหาภาพดีๆ แคปไปแคปมาได้แค่นี้แหละ

นากาฮามา เนรุ

เนรุนี่ยังพยายามแยกอยู่ว่าจริงๆ น่ารักเป็นธรรมชาติ หรือว่าทำเป็นคาแรคเตอร์ (แบบมิวสิค BNK48) ซึ่งจะเห็นชัดมากใน KEYABINGO 3 ที่ตอบคำถามผิดแล้วเอามือจับหัว คือมันก็น่ารักแต่ก็ไม่เกินวิสัยท่าของคนปกติไปอย่างที่มิวสิคทำ

ส่วนด้านเพลงนี่หลังจากได้ฟัง Hyakunenmateba ในอัลบั้มแล้วก็รู้สึกว่ามันดีมากๆ ไม่แง้วๆ เหมือน Noriokureta Bus แล้ว เปิดทีแรกยังไม่รู้สึกตัวเลยว่าเป็นเนรุโซโล่

เสียดายว่าพอฮิรางานะเริ่มมีบทบาทแล้ว เนรุดันโดนแยกไปเล่นเป็นฝั่งฮิรางานะซะเยอะ ซึ่งก็ลงไปเล่นเองไม่ได้เพราะต้องดันเด็กๆ ด้วย ก็จะออกมาเป็นภาพของกัปตันมากกว่า

กำลังคิดอยู่ว่าถ้าไปงานจับมืออาจจะไปเลนเนรุ คิดว่าเนรุน่าจะพอพูดภาษาอังกฤษได้มั้ง…

นากาซาว่า นานาโกะ (นกซว)

สรุปว่าหลังจากตอนแข่งกินแล้ว นากาซาว่าคุงก็ได้ขึ้นมาอยู่ในโอชิสักที

ก็ไม่รู้ว่าหมดเฟสดันนากาซาว่าคุงไปแล้วจะได้อยู่อีกนานมั้ย

โอเซกิ ริกะ

โอเซกิหลังๆ ไม่ค่อยมีบทในรายการเท่าไร ก็ยังดีว่าคาราโอเกะได้ออกมาเล่นให้เฮฮาบ้าง “คนที่เคยฟังหนูร้องเพลงคิดว่ามีไม่กี่คนเองนะคะ”

เอาจริงๆ โอเซกิก็เป็นคนที่ทำให้เราสงสัยอยู่เหมือนกันว่ายังไงคือโอชิ คือโอเซกิก็น่ารัก แต่สมมุติว่ามีโฟโต้เซตเลือกรูปได้ขายในไทยนี่ก็คงไม่คิดว่าจะซื้อ (แต่ถ้าสุ่มได้ก็เก็บ)

พูดถึงโฟโต้เซตแล้วแอบบอกว่ากำลังจะได้คอมพ์ 2 โอชิมาอยู่ล่ะ…

อิมาอิสึมิ ยุย (ซือมิน)

หลังซือมินพักงานแล้วก็ไม่เห็นเลย ก็ได้แค่หวังว่าเพลงยุยจังส์หน้าจะยังโอเคอยู่ ส่วน Natsunohanawaฯ นี่ยังไม่ปิ๊งเท่าไร คงเพราะเป็นเพลงช้าที่เราไม่ชอบฟัง

สุไก ยูกะ

ไม่รู้จะเขียนอะไรให้กัปตันดี แต่ก็รู้สึกว่าหลังไปคบกับฮาบุอาจจะมีโอกาสหลุดออกก็ได้

อยู่ระหว่างสังเกตการณ์

สุซุโมโต้ มิยู (มงตะ)

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

ยังนึกไม่ออกว่าทำยังไงมงจะขึ้นไป ถ้ารายการดันก็อาจจะได้ขึ้นไป แต่จะตกกลับมาหรือเปล่าก็ไม่รู้

อุเอมุระ รินะ (ป้ามู)

ก็เพิ่งมารู้ไม่นานว่าป้ามูนี่ก็ 20 แล้วถึงหน้าจะไม่ให้ก็ตาม หลังๆ ก็พอมีบทบ้างเช่น Keyabingo ตอนที่ให้บอกรักแล้วอุเอมุระกระโดดแบบลูกเสือ แต่ก็ยังไม่มีอะไรท็อปฟอร์มที่จะได้ขึ้นไป rank ถัดไป

โยเนทานิ นานามิ (โยเนะซัง)

โยเนะซังหลังๆ เริ่มหมดบทไปหลังจากเฟสดันนะ ส่วนตำแหน่งในลิสต์ก็อยู่ที่เดิมตลอดและไม่คิดว่าจะได้ขยับ ไว้อาจจะต้องคิดชื่อหมวดใหม่ เพราะมันไม่ใช่แค่ระหวังสังเกตการณ์ แต่มันคืออะไรที่อยู่ระหว่างโอชิกับเฉยๆ

เฉยๆ

ฮิราเตะ ยูรินะ (เทะจิ)

ผมแอบเห็นอัลบั้มนึงที่เป็นรูปเทะจิสมัยอัลบั้มก่อนๆ ที่เทะจิยังร่าเริงหัวเราะได้อยู่ ผมว่าความเป็นเด็กน้อยแบบนั้นดูน่ารักเลยนะ อยากเห็นเทะจิแบบนั้นเยอะๆ

แต่ตอนนี้เทะจิในรายการแทบไม่ได้ออกเลย ออกมาก็เงียบๆ ง่วงๆ

ฮาบุ มิซึโฮะ

ฮาบุตกจากแรงค์ข้างบนมา แต่จริงๆ น่าจะเรียกว่าเราจัดอันดับพลาดซะมากกว่า คือเสียงแบบฮาบุและคาแรคเตอร์โอตาคุที่ทีมงานไม่ดันเลย คิดว่าขายไม่ได้อ่ะ

วาตานาเบะ ริกะ (พี่เป)

ตั้งแต่เห็นเบื้องหลังในซีดีแล้วพบว่าพี่เปพูดได้ ก็รู้สึกว่าจริงๆ บางจังหวะพี่เปดูน่าจะพูดได้นะแต่โดนแย่งซะก่อน ดูจากสัมภาษณ์ Tokyo Girls Collection 2017 แล้วก็ยังคิดว่างานนอกพี่แกก็พูดไม่ได้แหละ

แต่ที่แปลกใจมากคือมีคนเล่าในคอมเมนต์ว่ามีคนไทยไปจับมือพี่เปแล้วบอกว่าจริงๆ พี่เปพูดมาก ก็อยากเห็นเหมือนกัน ถ้าไปจับมือคงไปลองถึงคงจะฟังไม่ออกสักคำ

ตอนที่สัมภาษณ์ครอบครัว น้องพี่เป (หรือเปล่านะ) ก็บอกว่าเห็นเงียบๆ จริงๆ พูดมากแล้วก็มุกนี่ระดับท็อปเลยนะ ตอนแรกก็ไม่เชื่อหรอกจนพอสังเกตตอนที่เล่นเกมกันแล้วพี่เปได้ไปถ่ายนอกสถานที่ มีควิซถามว่า “อะไรชื่อว่าเหมือนซาวาเบะ” พี่เปเล่นถาม “เป็นคนหรือเปล่าคะ!” ซาวาเบะถึงกับต้องถามว่าใครมันกล้าถามแบบนี้ คือมุกแบบนี้นี่เทพมากๆ มันฉีกทุกกฎเลย คิดว่า Revolution แก้เมื่อยนั่นก็น่าจะมุกแต่ดันโดนเบรค ถ้ารายการเปิดโอกาสนิดนึงพี่เปจะน่าสนใจมาก

ฮาราดะ อาโอย

ไม่เห็นรายการเอาฮาราดะมาออกเลย ตอนที่จับคู่ไปเดทก็ไม่ได้แม้แต่จะออกมา ไม่รู้ว่าทำไมเหมือนกัน

แต่ฮาราดะคงไม่รู้จะเล่นอะไรแล้วมั้ง คือมุกเด็กประถมก็เล่นจนบางคนอาจจะเบื่อแล้ว (เรายังไม่เบื่อนะ) แล้วน้องก็ ม. 5 แล้วด้วย (ถึงจะยังดูเป็นเด็กประถมแล้วก็ตาม)

ชิดะ มานากะ (โคลอี้ โมนะ)

ที่เหลือตั้งแต่ชิดะลงมามันไม่ค่อยมีอะไรเปลี่ยนแปลงจนไม่รู้จะเขียนอะไร…

โคอิเคะ มินามิ (มี่จัง)

Not interested

อิชิโมริ นิจิกะ

Reuse รูปกันง่ายๆ แบบนี้เลย

โอดะ นานะ

ซาโต้ ชิโอริ

เพิ่งมารู้ว่าแฟนๆ เรียกเค้าว่าซาโตชิ รู้สึกตกข่าว (คือเคยเห็นคนเรียกแต่ไม่เคยคิดว่าคือซาโต้)

ไซโต้ ฟูยูกะ (ฟูจัง)

ฟูจังเริ่มมีบทแล้ว แต่ก็จะซีเรียสๆ หน่อย (จริงๆ กัปตันควรจะทำได้ แต่แชปแชวงนี้ดันเป็นพงคตสึ)

โมริยะ อากาเนะ (รองโม)

สรุป

สรุปแล้วที่เขียนไม่มีอะไร แค่จะบอกว่ายุยปงเป็นคามิใหม่เราเท่านั้นแหละ

จบ

Small Scale Kubernetes Part III: ทดลองติดตั้ง Sentry

หลังจากตอนที่แล้วเราก็ได้ทำความรู้จักกับศัพท์เทคนิคใน Kubenetes ไปแล้ว ตอนนี้เราจะมาลองดูกันครับว่าการติดตั้งโปรแกรมจริงๆ ใน Kubernetes จะทำยังไงบ้าง

สำหรับวันนี้เราจะลองติดตั้ง Sentry ซึ่งเป็นบริการจับ exception กันดูครับ

System requirements

เวลาติดตั้ง Sentry จะประกอบด้วย

  • PostgreSQL เป็น Database server (Sentry 8 ไม่รองรับการติดตั้งใน MySQL แล้ว)
  • Redis
  • ตัว Sentry เองเป็นแอพ Django ซึ่งจะแบ่งเป็นส่วนย่อยๆ ดังนี้
    • Web server
    • Worker สำหรับประมวลผล event
    • Cron

ติตตั้ง Postgres

สำหรับ Postgres นั้นจะติดตั้งจาก Docker Hub ได้เลย โดยจะติดตั้งด้วย Replication Controller ไม่ใช้ Deployment เนื่องจากว่ามันทำ Rolling deploy ไม่ได้อยู่ดี (ถ้าอยากลองทำเป็น Deployment ก็ทำได้ไม่ผิดเช่นกัน)

ปกติแล้วไฟล์ config Kubernetes ทั้งหมดของผมจะถูกเก็บไว้ใน Git Repo แยกแต่ละ cluster ไป เพื่อให้สะดวกในการ track changes สำหรับไฟล์นี้ก็ไว้ที่ kube/postgres/postgres.yaml ซึ่งจริงๆ แล้วก็สามารถตั้งชื่อไฟล์ได้ตามสะดวก

apiVersion: v1
kind: ReplicationController
metadata:
  name: postgres
spec:
  replicas: 1
  selector:
    app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:9.6.4
        ports:
        - containerPort: 5432
        resources:
          requests:
            memory: "256Mi"
            cpu: "10m"
        readinessProbe:
          tcpSocket:
            port: 5432
        volumeMounts:
        - mountPath: /var/lib/postgresql/data
          name: postgres
        env:
        - name: POSTGRES_PASSWORD
          value: postgres
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
      volumes:
      - name: postgres
        gcePersistentDisk:
          pdName: postgres
          fsType: ext4

อย่าเพิ่งโหลดเข้าไปนะครับ จะเห็นว่าเรามีการกำหนด volume ด้วยซึ่งจะชี้ไปที่ disk ชื่อ postgres ฉะนั้นเราจะต้องเข้าไปสร้างใน console เสียก่อน โดยกำหนดชื่อให้ตรงกับ pdName ก็คือ postgres และที่สำคัญมากคือจะต้อง Zone ให้ตรงกับเครื่องเรา

เมื่อสร้างเสร็จแล้วก็สามารถโหลดเข้าไปได้เลยด้วยคำสั่ง kubectl apply -f kube/postgres/postgres.yaml

ถัดมาเราจะต้องสร้าง service ไว้ที่ kube/postgres/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: postgres
  labels:
    app: postgres
spec:
  selector:
    app: postgres
  ports:
    - port: 5432

ก็คือระบุว่า service postgres นั้น จะไปตามหา Pod ที่มี label app: postgres แล้วให้เชื่อมเข้าหา port 5432

Note เพิ่มเติม:

  • ปัจจุบัน Replication Controller ถูกแทนด้วย Replica Set แล้ว แต่ผมพบว่าการเขียน RC เขียนง่ายกว่า ก็เลยยังใช้อยู่
  • Kubernetes สามารถสร้าง disk ให้เราอัตโนมัติได้ด้วยถ้าเราเขียน PersistentVolumeClaim แต่ผมพบว่ามันจะซับซ้อนกว่า ก็เลยยังไม่ใช้ และถ้าจะเซตให้ GKE สร้าง SSD Disk ได้จะต้องเซตเพิ่มอีกด้วย
  • ใน production แล้วไม่แนะนำให้สร้างของ stateful ไว้ใน Kubernetes เช่น database server, cache ที่มีการ save ลง disk เนื่องจากว่ามันใช้ความสามารถในการ scale ของ Kubernetes ไม่ได้เลย (เพราะต้องมี disk) และเวลา Kubernetes สร้าง Pod ใหม่ก็จะต้องถอด Disk ต่อเข้าใหม่ (ถึงจะอยู่ในเครื่องเดิม) ซึ่งใช้เวลาพอสมควร
    • แต่สำหรับบทความนี้แล้วเราจะไม่เอางบไปสร้างเครื่องเพิ่ม ฉะนั้นก็ใส่ไว้นี้นี่แหละ

ติดตั้ง Redis

สำหรับ Redis ก็จะติดตั้งคล้ายๆ กันเลย ก็คือมีไฟล์ kube/redis/redis.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  name: redis
spec:
  replicas: 1
  selector:
    app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:32bit
        ports:
        - containerPort: 6379
        resources:
          requests:
            memory: "64Mi"
            cpu: "3m"
        readinessProbe:
          tcpSocket:
            port: 6379

และ kube/redis/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: redis
  labels:
    app: redis
spec:
  selector:
    app: redis
  ports:
    - port: 6379

เสร็จแล้วก็ติดตั้งเข้าไปได้เลย

kubectl apply -f kube/redis/redis.yaml
kubectl apply -f kube/redis/service.yaml

ติดตั้ง Sentry

ส่วน Sentry นั้น ขั้นแรกจะต้องสร้าง Secret key ขึ้นมาก่อนด้วยคำสั่ง kubectl run --restart=Never -i --rm --image=sentry sentry config generate-secret-key (เทียบเท่า docker run --rm sentry config generate-secret-key)

$ kubectl run --restart=Never -i --rm --image=sentry sentry config generate-secret-key
suutz#6a439htslt53uhd=%!8z&uzy2-knywxt!m!*4v!7rdwu
$  echo -n 'suutz#6a439htslt53uhd=%!8z&uzy2-knywxt!m!*4v!7rdwu' | base64 -w0
c3V1dHojNmE0MzlodHNsdDUzdWhkPSUhOHomdXp5Mi1rbnl3eHQhbSEqNHYhN3Jkd3U=

Tip: ใส่ space 1 ตัวหน้า command เพื่อไม่ให้เก็บใน history

จากนั้นเราจะเอา secret อันนี้ไปเก็บไว้ในไฟล์ Secret ที่ kube/sentry/secrets.yaml

apiVersion: v1
kind: Secret
metadata:
  name: sentry
type: Opaque
data:
  secret: c3V1dHojNmE0MzlodHNsdDUzdWhkPSUhOHomdXp5Mi1rbnl3eHQhbSEqNHYhN3Jkd3U=

Secret จะเป็น key-value ซึ่งใน data นั้นเราสามารถระบุ key เป็นอะไรก็ได้ (แต่จะต้องถูกรูปแบบ DNS name ด้วย) และส่วนของ value นั้นจะต้องระบุเป็นแบบ Base64 เพราะเราสามารถเก็บ secret ที่เป็น binary ได้ด้วย

(Note: ปกติแล้วเราจะเก็บรหัส database ไว้ในนี้ด้วย แต่เนื่องจากเราไม่ได้สร้าง user ใหม่ให้ postgres ก็เลยไม่มีรหัสให้เก็บ จะลองทำดูก็ได้ครับ)

โหลด Secret เข้าไปด้วยคำสั่ง kubectl apply -f kube/sentry/secrets.yaml

เสร็จแล้วก็สร้าง Deployment ของ Sentry ขึ้นมาที่ kube/sentry/sentry.yaml

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: sentry
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: sentry
    spec:
      containers:
      - name: sentry
        image: sentry:8.19.0
        resources:
          requests:
            memory: "64Mi"
            cpu: "1m"
        ports:
        - containerPort: 9000
        readinessProbe: &probe
          httpGet:
            path: /_health/
            port: 9000
        livenessProbe: *probe
        env:
        - name: SENTRY_POSTGRES_HOST
          value: postgres
        - name: SENTRY_DB_NAME
          value: postgres
        - name: SENTRY_DB_USER
          value: postgres
        - name: SENTRY_DB_PASSWORD
          value: postgres
        - name: SENTRY_REDIS_HOST
          value: redis
        - name: SENTRY_SECRET_KEY
          valueFrom:
            secretKeyRef:
              name: sentry
              key: secret
        volumeMounts:
        - mountPath: /var/lib/sentry/files
          name: sentry
      volumes:
      - name: sentry
        gcePersistentDisk:
          pdName: sentry
          fsType: ext4

อย่าลืมสร้าง Disk ชื่อ sentry และโหลดเข้าไปด้วยคำสั่ง kubectl apply -f kube/sentry/sentry.yaml

สร้าง service ให้ Sentry ที่ kube/sentry/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: sentry
  labels:
    app: sentry
spec:
  selector:
    app: sentry
  ports:
    - port: 80
      targetPort: 9000

ซึ่งจะกำหนดว่า port 80 ของ service IP ให้ map ไปที่ port 9000 ของ Sentry เสร็จแล้วก็โหลดด้วยคำสั่ง kubectl apply -f kube/sentry/service.yaml

สุดท้ายสร้าง cron และ worker ขึ้นมา ซึ่งจะใช้ config คล้ายๆ กัน แบบนี้

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: sentry-worker
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: sentry-worker
    spec:
      containers:
      - name: sentry-worker
        image: sentry:8.19.0
        args: [run, worker]
        resources:
          requests:
            memory: "64Mi"
            cpu: "1m"
        env:
        - name: SENTRY_POSTGRES_HOST
          value: postgres
        - name: SENTRY_DB_NAME
          value: postgres
        - name: SENTRY_DB_USER
          value: postgres
        - name: SENTRY_DB_PASSWORD
          value: postgres
        - name: SENTRY_REDIS_HOST
          value: redis
        - name: SENTRY_SECRET_KEY
          valueFrom:
            secretKeyRef:
              name: sentry
              key: secret
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: sentry-cron
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: sentry-cron
    spec:
      containers:
      - name: sentry-cron
        image: sentry:8.19.0
        args: [run, cron]
        resources:
          requests:
            memory: "64Mi"
            cpu: "1m"
        env:
        - name: SENTRY_POSTGRES_HOST
          value: postgres
        - name: SENTRY_DB_NAME
          value: postgres
        - name: SENTRY_DB_USER
          value: postgres
        - name: SENTRY_DB_PASSWORD
          value: postgres
        - name: SENTRY_REDIS_HOST
          value: redis
        - name: SENTRY_SECRET_KEY
          valueFrom:
            secretKeyRef:
              name: sentry
              key: secret

(สามารถใช้ --- คั่นระหว่างเอกสารใน yaml แล้วโหลดทีเดียวพร้อมกันก็ได้)

Note: โดยปกติแล้วถ้ามี environment ที่แชร์กันเรามักจะเขียน ConfigMap

สร้าง Database

ในการติดตั้ง Sentry จะต้องโหลด database เข้าไป

$ kubectl get pods
NAME                            READY     STATUS    RESTARTS   AGE
postgres-5d9j4                  1/1       Running   0          28m
redis-3r5wz                     1/1       Running   0          26m
sentry-1066741037-pkwbb         1/1       Running   0          2m
sentry-cron-2987324329-l6tll    1/1       Running   0          2m
sentry-worker-415181135-gxvg3   1/1       Running   0          3s
$ kubectl exec -it exec -it sentry-1066741037-pkwbb -- bash
root@sentry-1066741037-pkwbb:/# sentry upgrade
Syncing...
Creating tables ...
Creating table django_admin_log

....


Created internal Sentry project (slug=internal, id=1)

Would you like to create a user account now? [Y/n]: Y
Email: ใส่อีเมล
Password: ใส่รหัส
Repeat for confirmation: ใส่รหัส
Should this user be a superuser? [y/N]: y
User created: 
Added to organization: sentry
 - Loading initial data for sentry.

...

root@sentry-1066741037-pkwbb:/# exit

เสร็จแล้วก็ลองเข้า Sentry ดูได้เลยจากคำสั่ง kubectl --namespace sentry port-forward sentry-1066741037-pkwbb 9000 ซึ่งจะทำให้ kubectl forward port 9000 บนเครื่องเราเข้าไปบน Pod

ลองล็อคอินได้ แต่อย่าเพิ่งติดตั้ง Sentry เพราะเราจะเอา Sentry ขึ้นเป็นเว็บสวยๆ ก่อน

Reverse proxy

ปกติแล้วเวลาเราจะเปิด port ภายนอกใน Kubernetes เราจะใช้ service ประเภท LoadBalancer หรือใช้ Ingress เพื่อให้ Kubernetes สร้าง Load balancer ให้ แต่เนื่องจาก Load balancer ราคาแพงมากเราเลยจะเปิด port ที่เครื่องโดยตรงให้วิ่งเข้าสู่ Reverse proxy ภายใน

Reverse proxy ที่เราจะใช้คือ Traefik ซึ่งเขียนขึ้นในภาษา Go มันจะเข้าไปอ่าน Ingress ของ Kubernetes ให้เรา ทำให้ไม่ต้องแก้ไข config เวลาใช้งาน และถึงจะเป็นของใหม่ Traefik ก็พิสูจน์จากการใช้งานบน production ทุกระบบของ Wongnai แล้ว

เราจะติดตั้ง Traefik ด้วย RC ดังนี้ครับ kube/traefik/traefik.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  name: traefik
  namespace: sentry
spec:
  replicas: 1
  selector:
    app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      hostNetwork: true
      containers:
      - name: traefik
        image: traefik
        args:
        - --web
        - --kubernetes
        ports:
        - containerPort: 80
        - containerPort: 443
        - containerPort: 8080
        resources:
          requests:
            memory: "64Mi"
            cpu: "1m"
        readinessProbe: &probe
          httpGet:
            path: /health
            port: 8080
        livenessProbe: *probe

การกำหนด hostNetwork: true จะทำให้ kubernetes เปิดด้วยโหมด --net=host ทำให้เราเปิด port ได้เลย

ถ้าต้องการให้ Traefik ออก Let’s Encrypt อัตโนมัติ ก็สามารทำได้โดยเพิ่ม args ดังต่อไปนี้

        args:
        - --web
        - --kubernetes
        - --acme
        - --acme.acmelogging
        - --acme.storage=/certs/traefik.json
        - --acme.email=กรอกอีเมล
        - --acme.entrypoint=https
        - --acme.onhostrule
        - --entryPoints=Name:https Address::443 TLS
        - --entryPoints=Name:http Address::80 Redirect.EntryPoint:https
        - --defaultentrypoints=http,https

และอย่าลืม mount /certs/ เข้าไปด้วย โดยการตั้งค่านี้จะทำให้เมื่อเราสร้าง host ใหม่แล้ว Traefik จะออก Let’s Encrypt ทันที และให้ redirect HTTP -> HTTPS อัตโนมัติ

(ในบทความนี้จะยังไม่ใช้ฟีเจอร์นี้นะครับ แต่ไปลองเล่นเองได้)

ถัดมา เพื่อความปลอดภัย (ของกระเป๋าตัง) เราจะต้องปิด Google Cloud Ingress Controller เพื่อป้องกันไม่ให้ Kubernetes สร้าง Load balancer อัตโนมัติ

$ gcloud container clusters update cluster-name --update-addons=HttpLoadBalancing=DISABLED
Updating cluster-name...done.                                                                                                                                                                                            
Updated [https://container.googleapis.com/v1/projects/project-name/zones/asia-southeast1-a/clusters/cluster-name].

ลองเช็คใน kubectl --namespace kube-system get pods ว่าไม่มี l7-default-backend

สุดท้ายเราจะสร้าง Ingress เพื่อกำหนดว่าให้ forward URL ที่กำหนดเข้าไปที่ Sentry

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: regist
  annotations:
    kubernetes.io/ingress.class: "traefik"
spec:
  rules:
  - host: sentry.mysite.com
    http:
      paths:
      - path: /
        backend:
          serviceName: sentry
          servicePort: 80

เสร็จแล้วก็เข้าไป เปิด port 80 และลองเข้าเว็บดูก็จะสามารถเริ่มใช้งาน Sentry ได้เลย

Small scale cluster

ประเด็นสำคัญที่ทำให้อยากเขียนเรื่อง Small scale ก็คงเป็นว่าอยากแชร์วิธีการใช้ Kubernetes โดยไม่ใช้ Load balancer นี่ล่ะครับ เพราะถ้าเป็นบริษัทการจะซื้อ Load Balancer ไม่ใช่เรื่องแปลก โดยเฉพาะบน cloud แต่พอเราใช้เครื่องๆ เดียวแบบนี้แล้ว การซื้อ Load balancer กลายเป็นค่าใช้จ่ายที่อาจจะแพงกว่าเครื่องเสียอีก

แล้ว Kubernetes ก็พยายามผลักดันเหลือเกินให้เราซื้อให้ได้ ไม่ว่าจะเป็น service หรือ ingress ที่สั่งทีเดียวได้ load balancer มาเลย ตอนผมตัดสินใจว่าเว็บจะใช้ Kubernetes ก็เลยต้องมานั่งนึกข้อดี-ข้อเสียอยู่ ว่าเราจะใช้ Kubernetes ทำไมถ้าจะดื้อกับวิธีของมันแบบนี้ เราใช้ Docker Compose แบบเดิมไม่ได้หรอ

ประเด็นสำคัญที่ยังเลือก Kubernetes อยู่ก็คงเป็นเรื่องของ Rolling deploy ที่อยากทำนานแล้ว แต่ Compose ทำไม่ได้, เรื่องว่าจะเอาไว้ทดลองเล่น Kubernetes ด้วย และก็เป็นทางเลือกที่ future proof ดีว่าจะขยายระบบในอนาคตก็แค่แก้ตัวเลขไม่กี่ที่ก็ได้แล้ว

ถามว่ามีปัญหามั้ยกับการเปิด port ตรงๆ แบบนี้ ก็บอกเลยว่า downtime เป็นเรื่องหลีกเลี่ยงไม่ได้เลยครับ เพราะว่า

  • เราไม่มีเครื่อง spare เลย เวลา node upgrade ก็คือต้อง schedule downtime อย่างเดียว
  • และจะเปิด 2 เครื่องตอนอัพเกรดก็ไม่ได้เพราะว่าไม่มี load balancer ถ้า ip เครื่องหลักล่มไปก็บ๊ายบาย
    • ไว้มีเวลาจะลองวิจัยตรงนี้ดูว่า minimize downtime ได้มั้ย พอมี solution ในหัวอยู่ที่อยากลอง automate ดู
  • และเรา rolling deploy ตัวโปรแกรมที่อยู่ port 80 ไม่ได้เลย (ในตัวอย่างคือ traefik) เพราะมันไม่ได้ใช้ service ip ที่ยังมีการ route traffic ไป pod อื่นได้
    • ของ TipMe ใช้ Caddy อยู่ ถ้าจะแก้ forwarding rule นี่คือ restart อย่างเดียวเลย

ซึ่งก็เป็น tradeoff ที่คิดว่ายอมรับได้ เพราะเว็บเราคงไม่จำเป็นจะต้อง 100% up จะมี scheduled downtime บ้าง ก็ยังพอรับได้

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