my space

docker-compose และ wait-for

dockerspring-bootdocker-composewait-for

July 24, 2020

ช่วงนี้ยังวุ่นๆกับ Docker และ spring boot อยู่เหมือนเดิม เนื่องจากโปรเจคที่ทำนั้นออกแบบให้พัฒนาแบบ microservice ก็เลยทำให้มี project เล็กๆอยู่เต็มไปหมด การจะไปค่อยๆ ทำ docker build เพื่อสร้าง image ในแต่ละโปรเจคนั้นก็ดูจะลำบากเกินไป (และปวดหัวมาก) เลยมองหา solution ที่ docker เตรียมไว้ให้ นั้นคือใช้ docker-compose นั่นเอง

Commands คำสั่งทั่วๆไป

docker-compose มีประโยชน์มากๆเวลาต้องจัดการ docker หลายๆ container พร้อมกัน เราสามารถสั่ง (โดย docker จะไปดู image จาก docker-compose.yml โดย default)

  • push เพื่อ push images เข้าไปใน repo
  • pull เพื่อ pull images จาก repo
  • up เพื่อเอา image มาสร้าง container และ run
  • down เพื่อหยุด container ยังมีคำสั่งอื่นๆให้เล่นอีก แต่ list หลักๆไว้ประมาณนี้

ตัวอย่าง docker-compose.yml เช่น

version: "3.8"

services:
  myproject.database:
    build:
      context: "../database"
      dockerfile: Dockerfile
    image: myproject/database:latest
    environment:
      POSTGRES_PASSWORD: Welcome
    volumes:
      - ../../../data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
  myproject.config:
    build:
      context: "../config"
      dockerfile: Dockerfile
    image: myproject/config:latest
    ports:
      - "8888:8080"
    depends_on:
      - myproject.database
  myproject.service-discovery:
    build:
      context: "../service-discovery"
      dockerfile: Dockerfile
    image: myproject/service-discovery:latest
    ports:
      - "9001:9001"
    depends_on:
      - myproject.config
  myproject.gateway:
    build:
      context: "../gateway"
      dockerfile: Dockerfile
    image: myproject/gateway:latest
    ports:
      - "8080:8080"
    depends_on:
      - myproject.service-discovery
    volumes:
      - ../../../logs:/logs

การ set up environment

docker-compose ยังจะช่วยเราในการจัดการ environment อีกด้วย ยกตัวอย่างจาก docker-compose.yml ด้านบน เรามี image myproject.database ซึ่งเป็น database ของเครื่องที่ microservice ตัวอื่นๆจะต้องไปเรียกใช้งาน ซึ่งใน microservice เหล่านั้นก็สามารถอ้างถึง database ผ่าน url myproject.database ได้เลย ไม่ต้องไปตามหา ip ให้วุ่นวาย

นอกจากนั้นยังช่วยเรื่องการ map directory จาก docker container ออกมาบน host ที่รันมันได้อีกด้วยผ่านทาง volumnes:

depends_on และ wait-for

ยังมีเรื่อง depends_on ที่ช่วยเรื่องการรัน container ก่อนหลัง ซึ่งในที่นี้ docker-compose จะยังไม่ฉลาดขนาดไปรู้ได้ว่า service ที่มัน depends on นั้น start เสร็จหรือยัง มันจะรู้แค่คล้ายๆว่า docker container นั้นถูกเปิด switch ให้ทำงานเท่านั้น ซึ่งก็นำมาสู่ปัญหาถัดไปก็คือ…

ถ้า microservice มันต้องมีการเรียกใช้งานกัน เช่น service-discovery ต้องการดึง configuration จาก config แต่ถ้า config ยัง start ไม่สำเร็จ service-discovery ก็จะไปดึง service จาก default ของมันและไม่สนใจ config อีกเลย… แบบนี้จะทำให้โปรแกรมทำงานผิดพลาดได้ หรือ config บอกว่าต้องการใช้งาน database แต่ถ้า database ยัง start ไม่สำเร็จแล้ว config ดัน start ขึ้นมาแล้วไม่เจอ database ตัว config เองอาจจะหยุดการทำงานไปเลย เพราะถือว่าเป็น fatal error

ในกรณีนี้เราไปเจอ solution ที่ชื่อว่า wait-for ซึ่งเป็น shell script ธรรมดาที่เขียนไว้ให้ช่วยวน loop check จนกว่า service นั้นๆจะขึ้นแล้วถึงค่อยให้ทำงาน เท่าที่หาเจอมาก็มี 2 ตัวที่คนใช้เยอะๆ

ปกติถ้าเจอแบบนี้จะเลือกใช้ตัวที่ star เยอะกว่า (แบบมึนๆไปเลย) แต่ตอนนี้ใช้ wait-for เป็นหลัก สาเหตุก็เพราะว่า wait-for เขียนบน sh ซึ่งเป็น shell ที่มีบน alpine linux (image ส่วนมากของเรา base อยู่บนนั้น) แต่ wait-for-it นั้นอยู่บน bash ถ้ารันตรงๆจะไม่ผ่าน อาจจะต้องไป get bash มาอีก ก็จะลำบากขึ้น… ตอนนี้เลยยังใช้ wait-for

wait-for

เราเอา wait-for นี้ผนวกรวมเข้าไปในตอน build docker image ด้วยเลย ตัวอย่าง Dockerfile จะเป็นแบบนี้

FROM openjdk:8-alpine3.8

EXPOSE 8080

ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar

COPY wait-for.sh wait-for.sh
RUN chmod 777 wait-for.sh

ENTRYPOINT ["./wait-for.sh", "myproject.config:8080", "--timeout=300", "--" , "java","-jar","/app.jar", "--spring.profiles.active=alpha"]

จากตัวอย่างนี้จะเห็นว่าเราแค่ copy wait-for เข้าไป และใน ENTRYPOINT เราเรียก wait-for. sh ให้ช่วยเช็คจนกว่า myproject.config:8080 จะขึ้นมา หลังจากนั้นเราถึงจะ start project ของเราด้วย java --jar /app.jar --spring.profiles.active=alpha แต่ถ้าหากเกิน 300 วินาทีก็จะ timeout และตายไป


gie

Written by gie who lives and works in Bangkok. Build things by code.
my twitter | github