Skip to content

13.4 docker-compose topology

บทนี้เจาะจง docker-compose ของทั้งระบบ ซึ่งเป็น orchestration หลักของ production — ไม่ใช่เครื่องมือพัฒนาเท่านั้น

สรุปโครงสร้าง docker-compose

Repoไฟล์จำนวน servicenetwork modereplica หลัก
vtrc-apidocker-compose.yml7 (api ×4, web-proxy, redis, pma)nginx เท่านั้น host; api/redis/pma = bridge + ports:4 (APP_NO=1,2,3,5)
vtrc-apidocker-compose.dev.yml(dev variant)
cu-central-apidocker-compose.yml5 (api ×3, web-proxy, redis)nginx เท่านั้น host; api/redis = bridge + ports:3 (APP_NO=1,2,3)
cu-central-apidocker-compose-dev.yml(dev variant)
centralize-apidocker-compose.yml2 (dev, prod)bridge + external network1
vtrc-rc-backofficedocker-compose.yml1 (nginx)bridge + external network1

vtrc-api — docker-compose หลัก

vtrc-api/docker-compose.yml:1-100

โครงสร้าง service:

vtrc-redis-db       (Redis 6379)         — build from docker-redis/
vtrc-web-proxy      (nginx)              — host network, port 80/443
vtrc-pma            (phpMyAdmin)         — port 8093 (มีใน compose แต่ nginx block ไว้)
vtrc-api            (Node, APP_NO=1)     — port 8081:8081
vtrc-api-2          (Node, APP_NO=2)     — port 8082:8081
vtrc-api-3          (Node, APP_NO=3)     — port 8083:8081
vtrc-api-5          (Node, APP_NO=5)     — port 8085:8081   (ข้าม APP_NO=4)

จุดสังเกตสำคัญ:

  1. network_mode: host ของ nginx (docker-compose.yml:34) — container ใช้ network ของ host โดยตรง ไม่มี port isolation เลย ทำให้ nginx สามารถเรียก localhost:8081, localhost:8082 เพื่อไปยัง Node replica ได้ — เป็นรูปแบบที่ตรงข้ามกับ best practice ของ Docker

  2. Node replica ทั้งหมดใช้ image เดียวกันimage: vtrc-api_vtrc-api:latest (docker-compose.yml:48,63,76,89) ต่างกันเพียงที่ APP_NO ที่ส่งเข้าไป

  3. ไม่มี environment variable สำหรับ DB credentials — docker-compose ไม่กำหนด DB_HOST, DB_PASSWORD หรือ JWT_SECRET ในส่วน environment: หมายความว่า config เหล่านี้ต้องฝังใน image เอง (ผ่านไฟล์ .env ที่ COPY ใน Docker build) หรืออยู่ใน config.js ที่ hard-coded

  4. Volume mount ระหว่าง replica — ทั้งสี่ replica ใช้ volume ./api-storage และ ./api-logs ร่วมกัน (docker-compose.yml:58-59,71-72,84-85,97-98) — ทำให้การเขียนไฟล์ (เช่น PDF payslip) มองเห็นได้ทุก replica แต่ก็มีความเสี่ยงเรื่อง race condition เมื่อหลาย replica เขียนไฟล์พร้อมกัน

  5. TZ=Asia/Bangkok ถูกตั้งในทุก replica — สำคัญเพราะ Node 12 ใช้ system timezone โดย default

vtrc-api — phpMyAdmin ที่ expose สาธารณะ

vtrc-api/docker-compose.yml:35-46:

yaml
vtrc-pma:
  image: phpmyadmin/phpmyadmin
  container_name: vtrc-phpMyAdmin
  restart: always
  ports:
    - "8093:80"
  environment:
    PMA_ABSOLUTE_URI: http://vtrcapi.redcross.or.th/db-management/
    PMA_HOST: vtrc-database
    UPLOAD_LIMIT: 100M

phpMyAdmin รันอยู่ใน compose ที่ expose port 8093 สาธารณะ — แม้ว่า nginx จะ comment block /db-management/ ไว้ (vtrc-api/nginx/default.conf:186-193) แต่ port 8093 เองยังเปิดอยู่

ปัญหานี้ถูกรายงานไว้ใน docs/audit/05-tech-debt-register.md (PERF-7) ว่าเป็น "Critical" — admin database surface อยู่ใน production network โดยไม่จำกัด IP

cu-central-api — docker-compose หลัก

cu-central-api/docker-compose.yml:1-93

โครงสร้าง service:

redis-db            (Redis 6379)         — build from Dockerfile-redis
rc-web-proxy        (nginx)              — host network, port 443 + 25563
cu-cen-api          (Node, APP_NO=1)     — port 8000:8000
cu-cen-api_2        (Node, APP_NO=2)     — port 8002:8000
cu-cen-api_3        (Node, APP_NO=3)     — port 8003:8000

จุดสังเกต:

  1. image: gcr.io/vtrc-nonprod/cu-centralize-api:latest — image ดึงจาก GCR ของ project vtrc-nonprod ทั้ง ๆ ที่ compose นี้ใช้ใน production (บ่งชี้ว่าใช้ GCR project เดียวกันทั้ง prod และ UAT)

  2. extra_hosts สำหรับ LDAPdocker-compose.yml:54-55,73-74,90-91:

    yaml
    extra_hosts:
      - "sso.redcross.or.th ssoapi.redcross.or.th:10.188.128.213"

    บอก container ให้ resolve ชื่อ sso.redcross.or.th ไปที่ IP on-prem โดยตรง — เป็นวิธีที่ใช้เชื่อม container ใน GCP เข้ากับ LDAP ของ on-prem

  3. NODE_ENV=development — ส่งค่า environment เป็น development แม้ใน compose production (docker-compose.yml:45,64,81) ทำให้ code ที่ตรวจสอบ IS_PRODUCTION ทำงานผิดพลาด — เช่น GraphQL playground จะเปิดทำงาน

  4. Port 25563docker-compose.yml:31 เปิด port 25563 ของ nginx เพิ่มเติมจาก 443 บ่งชี้ว่ามี endpoint ภายในหรือการเข้าถึงพิเศษผ่าน port นี้ (ดู cu-central-api/nginx/default.conf:65-107)

centralize-api — ใช้ external network

centralize-api/docker-compose.yml:1-32:

yaml
version: "3.8"
services:
  dev:
    build:
      dockerfile: Dockerfile
      target: develop
    ports:
      - "3000:3000"
    command: yarn start:dev
    networks:
      - cu-centralize-api_default
  prod:
    build:
      dockerfile: Dockerfile
      target: production
    ports:
        - "3000:3000"
    command: yarn start:prod
    networks:
        - cu-centralize-api_default
networks:
  cu-centralize-api_default:
    external: true

จุดสังเกต:

  1. External network cu-centralize-api_default — centralize-api เชื่อมต่อเข้ากับ network ที่สร้างโดย docker-compose ของ cu-central-api เพื่อให้ container สองระบบสามารถเรียกกันได้
  2. dev และ prod อยู่ในไฟล์เดียวกัน — ทีมต้องระบุ docker-compose up dev หรือ docker-compose up prod จึงจะรู้ว่าจะรัน service ไหน — โอกาสสับสนสูง
  3. ไม่มี restart: always — ถ้า container crash จะไม่ restart อัตโนมัติ

vtrc-rc-backoffice — nginx เท่านั้น

vtrc-rc-backoffice/docker-compose.yml:1-23:

yaml
version: '3.8'
services:
  web-server:
    image: nginx:latest
    container_name: nginx-web-server
    restart: always
    environment:
      TZ: Asia/Bangkok
    volumes:
      - ./build:/usr/share/nginx/html
      - ./meeting:/usr/share/nginx/meeting
      - ./nginx:/etc/nginx/conf.d
      - ./web-logs:/var/log/nginx
      - ./cert:/etc/sslcert
    ports:
      - 80:80
      - 443:443
    networks:
      - uat-vtrc-backoffice-network

networks:
  uat-vtrc-backoffice-network:
      external: true

จุดสังเกต:

  1. ./build:/usr/share/nginx/html — mount directory build ที่ build ด้วย CRA ไปที่ nginx ทำให้ไม่ต้อง build Docker image ใหม่เพื่อ deploy แค่ copy build ใหม่มาทับแล้ว reload nginx
  2. ./cert:/etc/sslcert — mount certificate SSL เข้า container ทำให้ต้องจัดการ renewal certificate บน host โดยตรง (อาจผ่าน certbot)
  3. Network name uat-vtrc-backoffice-network — ชื่อ network บ่งชี้ว่า compose นี้ใช้กับ UAT แต่ก็น่าจะใช้กับ production ด้วยเนื่องจากไม่มี compose แยก

network mode: host vs bridge — สรุปผลกระทบ

ความจริงจาก compose: network_mode: host ถูกตั้ง เฉพาะ service nginx (vtrc-web-proxy บรรทัด 34, rc-web-proxy บรรทัด 35) — Node API replica และ Redis ใช้ bridge ปกติแล้ว map port ออก host (8081:8081, 8000:8000, ฯลฯ) ดังนั้น nginx บน host network จึงเรียก localhost:8081 / localhost:8000 ได้

มิติhost network (nginx เท่านั้น)bridge + ports (API, Redis, PMA, NestJS, backoffice)
การเข้าถึง service อื่นlocalhost:PORT โดยตรง (เห็น port ที่ map บน host)ต้องผ่านชื่อ container หรือ IP / หรือผ่าน port map ของ host
ความปลอดภัยต่ำ — nginx container เห็น network ของ hostสูงกว่า — isolation โดย default
การจัดการ portใช้ port ของ host ตรง ๆmap ผ่าน ports:
ความสามารถในการ scaleจำกัด — ทุก replica ต้องใช้ host port ต่างกันดี — แต่ละ container มี IP ของตัวเอง

การใช้ network_mode: host ของ nginx เป็นเหตุผลหลักที่ upstream localhost:8081 / localhost:8000 ทำงานได้ — แต่ก็ทำให้การย้ายไป Kubernetes หรือ orchestration อื่นยากขึ้น

ปัญหาเชิงปฏิบัติการที่พบ

ปัญหาที่มาผลกระทบ
ไม่มี depends_on หรือ healthcheckทุก composeredis อาจยังไม่พร้อมเมื่อ Node เริ่ม start → retry ด้วยตนเอง
NODE_ENV=development ใน cu-central-api productioncu-central-api/docker-compose.yml:45,64,81code ที่ตรวจ IS_PRODUCTION ทำงานผิด
image: latest ทุก servicedocker-compose ทุกไฟล์ไม่สามารถ rollback ได้ด้วย image tag — ต้องดู tag ใน GCR
ไม่มี resource limit (mem_limit, cpus)ทุก composereplica หนึ่งที่ใช้ CPU/Memory สูงจะดึงทรัพยากรจาก host จนกระทบ replica อื่น
network_mode: host ใน nginxvtrc-api, cu-central-apiลด isolation ทำให้ย้ายไป orchestration อื่นยาก

ข้อแนะนำเพื่อปรับปรุง

  1. ใช้ bridge network แทน host — กำหนด ports: อย่างชัดเจน
  2. กำหนด depends_on + healthcheck — ให้ Node รอจนกว่า redis จะพร้อม
  3. ตั้ง NODE_ENV=production ใน compose ของ cu-central-api
  4. ใช้ image tag ที่กำหนดเวอร์ชัน แทน latest — เช่น gcr.io/.../vtrc-api:2026-07-11-abc1234
  5. กำหนด resource limit — ป้องกัน noisy neighbor problem ระหว่าง replica