การใช้ docker สำหรับเตรียมพื้นที่พัฒนาเว็บแอปพลิเคชัน
การพัฒนาเว็บแอปพลิเคชันในปัจจุบัน มีความซับซ้อนและความต้องการทางเทคนิคที่เพิ่มขึ้นมาก การพัฒนาจึงกลายเป็นการทำงานที่ต้องทำร่วมกันกับคนจำนวนมากขึ้น ไม่ได้จำกัดแค่คนกลุ่มเล็กเท่านั้น แต่อาจหมายถึงการรวมกลุ่มทำงานในทีมที่อาจมีขนาดถึง 50-100 คน การเตรียมชุดซอฟต์แวร์เพื่อให้ทีมงานจำนวนมากสามารถใช้ร่วมกันได้ โดยที่มีความใกล้เคียงกับพื้นที่จริงบน production environment จึงกลายเป็นความจำเป็น ซึ่ง docker สามารถช่วยตอบโจทย์ความต้องการนี้ได้ ทว่าการใช้งาน docker จำเป็นต้องเข้าใจพื้นฐานการเชื่อมต่อ network เพื่อให้สามารถตั้งค่าการใช้งานภายในโรแกรมได้เอง ตามที่กำลังจะอธิบายต่อไปด้านล่าง
การเชื่อมต่อผ่าน docker network
- หากไม่มีการตั้งค่าเพิ่มเติม docker จะทำงานภายในเครือข่าย network ในรูปแบบของ
bridge mode
network ที่ใช้ชื่อว่าbridge
# คำสั่ง cli ที่ใช้ในการดูรายละเอียดของ docker network # ดูรายชื่อ docker network docker network ls # ดูรายละเอียดของ docker network docker network inspect <ชื่อของ network> # ดูรายละเอียดของ docker network ค่าเริ่มต้น docker network inspect bridge
- การเชื่อมต่อผ่าน docker network ควรสร้าง network ชื่อเฉพาะของแต่ล่ะงานขึ้นมา
เพื่อแยกระบบออกจากการกันให้ชัดเจน
# ตัวอย่างการสร้าง network # สร้าง network ชื่อ myweb ➜ ~ docker network create myweb 94e2714b100d6634299bbbc7dfb30bf7bc7d150e27f4f48764bec91d32d4686e # ทดลองสร้าง container ขื่อ redis-store สำหรับ redis ขึ้นมาบน network ชื่อ myweb ➜ ~ docker run --name redis-store --network myweb redis:6-alpine 1:C # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 1:C # Redis version=6.2.6, bits=64, commit=00000000, modified=0, pid=1, just started 1:C # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf 1:M * monotonic clock: POSIX clock_gettime 1:M * Running mode=standalone, port=6379. 1:M # Server initialized 1:M * Ready to accept connections ➜ ~ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cb67ff6bd900 redis:6-alpine "docker-entrypoint.s…" About a minute ago Up About a minute 6379/tcp redis-store # ดูรายละเอียดของ docker network ค่าเริ่มต้น ➜ ~ docker network inspect myweb [ { "Name": "myweb", . # ของจริงมีรายละเอียดเยอะกว่านี้แต่ย่อมาให้เห็นเฉพาะส่วนของ containers . "Containers": { "cb67ff6bd90061869639e950e908b169d830e54173d479c9ff2186597d2fd1b7": { "Name": "redis-store", "EndpointID": "ed4aa0f9da01cbb7bfb7f6ae6a8eea8af8b58b5c66f16aef9b13831dc652fc22", "MacAddress": "02:42:ac:1b:00:02", "IPv4Address": "172.27.0.2/16", "IPv6Address": "" } } } ]
- จากตัวอย่างด้านบนจะเห็นได้ว่าเราสามารถเข้าถึง container ที่ชื่อว่า redis-store ได้ด้วย
IP address
172.27.0.2
แต่ทว่า IP address นี้จะเปลี่ยนไปทุกครั้งที่มีการ restart container ดังนั้นการนำค่า IP Address ไปใช้งานโดยตรงจึงไม่ใช่เรื่องที่ดีนักเพราะเราต้องเสียเวลามาเปลี่ยน IP Address อยู่เรื่อยๆ - วิธีการที่ถูกต้องคือควรจะนำแอปพลิเคชันที่เรียกใช้ redis ใส่ไว้ใน network ที่ชื่อ myweb ด้วย
และภายใน code ที่ทำการเชื่อมต่อก็ใช้ชื่อของ container แทน hostname, IP address ได้เลย
ในตัวอย่างนี้ code ที่ทำการเชื่อต่อ redis จะสามารถเชื่อมต่อกับ redis ผ่านตัวอย่าง URL ต่อไปนี้ได้
redis://172.27.0.2:6379
(ใช้ IP Address โดยตรงก็ได้แต่เป็นวิธีที่ไม่แนะนำ)redis://redis-store:6379
(ควรใช้ชื่อ container แทน hostname)
- หากในขั้นตอนด้านบน ไม่ได้ทำการสร้าง network และใช้ network ชื่อ
bridge
ตามค่าเริ่มต้น การใช้ชื่อ container แทน hostname แบบredis://redis-store:6379
จะไม่สามารถทำได้
การเข้าถึง container ด้วย port mapping
- ในกรณีที่เราต้องการเข้าถึงผ่าน
localhost
หรือ127.0.0.1
เราสามารใช้ใช้ option -p เพื่อทำการ port mapping ออกมาจาก docker network# แอปพลิเคชันที่อยู่ภายใน localhost จะสามารถเชื่อต่อได้ผ่าน redis://localhost:6379 docker run --name redis-store --network myweb -p 127.0.0.1:6379:6379/tcp redis:6-alpine # /tcp อนุโลมให้ไม่ใส่ได้เพราะค่าเริ่มต้นคือ tcp อยู่แล้ว และอีก protocol ที่ชื่อ udp ก็ไม่เป็นที่นิยมใช้ docker run --name redis-store --network myweb -p 127.0.0.1:6379:6379 redis:6-alpine
- จะสังเกตได้ว่าเราทำการ map ให้เข้ากับ host
127.0.0.1
ซึ่งข้อผิดพลาดหรือความไม่ระวังอย่างหนึ่งที่พบได้บ่อยคือการใช้ option -p โดยไม่ระบุ host ทำให้ container สามารถถูกเข้าถึงได้จาก host ภายนอกทั้งหมด# สังเกตตรง -p 6379:6379 ซึ่งเป็นลักษณะที่ไม่พึงประสงค์ ควรใช้ก็ต่อเมื่อเข้าใจความเสี่ยงเท่านั้น docker run --name redis-store --network myweb -p 6379:6379 redis:6-alpine
บันทึกและติดตามการเปลี่ยนแปลงการตั้งค่า docker ด้วย docker-compose
- การทำเว็บแอปพลิเคชัน องค์ประกอบจะเพิ่มความซับซ้อนมากขึ้นเรื่อยๆ ตามความต้องการของแอปพลิเชัน ซึ่งทำให้การตั้งค่า docker ก็ต้องมีการบันทึกเก็บไว้ใน version control เพื่อให้สามารถติดตามการเปลี่ยนแปลงได้
-
docker-compose ไม่ได้เป็นเพียงรูปแบบที่ช่วยบันทึก แต่เรายังสามารถเรียกใช้งานทุกองค์ประกอบได้โดยที่ไม่ต้อง
docker run
ทีล่ะ container ลดความยุ่งยากในการจำตัวเลือกการตั้งค่าต่างๆเหลือเพียงให้จำเพียงชื่อ service ของ container เท่านั้น# docker-compose.yml version: '3' services: web: build: context: . dockerfile: ./Dockerfile ports: - 8080:8080 # port mapping เพื่อให้สามารถใช้ localhost:8080 แทน internal IP ได้ volumes: - .:/var/www/html environment: - REDIS_URL=redis://redis:6379 networks: - myweb redis: image: 'redis:6-alpine' # ใช้สัญลักษณ์ # ในการ comment เพื่อให้ docker-compose ไม่อ่านค่า # comment port mapping ออก โดยสามารถนำเครื่องหมาย # ข้างหน้าออกเพื่อเปิดใช้งานได้ในกรณี # ที่ต้องการ debug # ports: # - 6379:6379 environment: - ALLOW_EMPTY_PASSWORD=yes networks: - myweb networks: myweb: driver: bridge
- สามารถตั้งค่า docker network จากภายในไฟล์
docker-compose.yml
ได้เลย โดยไม่จำเป็นต้องสร้าง network ด้วยคำสั่งdocker network create <ชื่อ network>
เอาไว้ก่อน - แต่หากต้องการใช้ network ที่ไม่ได้สร้างจากภายในไฟล์
docker-compose.yml
จำเป็นต้องระบุว่าเป็น external network ตามรูปแบบนี้# docker-compose.yml networks: external: myweb: driver: bridge
-
การใช้งานผ่าน docker-compose จะทำให้การเชื่อมต่อ redis จากภายใน code ของ container web สามารถใช้ชื่อ service
redis
แทน hostname ได้เลย เช่นredis://redis:6379
ทำให้ไม่มีความจำเป็นต้องจำชื่อ container อีกต่อไป - การเรียกใช้งานเบื้องต้น
# เรียกใช้งานในรูปแบบ active process สามารถ kill โดยการกด ctrl-C หรือ ปิด terminal ได้ docker-compose up # เรียกใช้งานในรูปแบบ daemon process docker-compose up -d # ตัวอย่างการเรียกใช้งานเฉพาะบาง service docker-compose up web docker-compose up redis # ตัวอย่างการเรียกใช้งานหลาย service แบบเจาะจง docker-compose up web redis
Environment Variables จากไฟล์ .env
- คำสั่ง docker-compose จะอ่านค่าจากไฟล์
.env
โดยอัตโนมัติ ทำให้ไม่จำเป็นต้องใส่ค่าบางค่า ที่ผู้พัฒนาบางคนอาจใช้ไม่ตรงกันได้ เช่น port ของแอปพลิเคชัน หรือ ชื่อของ network ที่ใช้ - การใช้ค่าจาก Environment variables ภายใน code เป็นแนวทางที่ถูกแนะนำภายใต้หลักการ 12-factor เพื่อให้การ deploy เว็บแอปพลิเคชัน สามารถแยกตาม environment ได้
# .env
EXPOSED_WEB_PORT=8080
WEB_DIRECTORY=.
REDIS_HOSTNAME=redis
EXPOSED_REDIS_PORT=6379
MY_WEB_NETWORK_NAME=myweb
# docker-compose.yml
version: '3'
services:
web:
build:
context: .
dockerfile: ./Dockerfile
ports:
- ${EXPOSED_WEB_PORT}:8080
volumes:
- ${WEB_DIRECTORY}:/var/www/html
environment:
- REDIS_URL=redis://${REDIS_HOSTNAME}:${EXPOSED_REDIS_PORT}
networks:
- myweb
redis:
image: 'redis:6-alpine'
# ports:
# - ${EXPOSED_REDIS_PORT}:6379
environment:
- ALLOW_EMPTY_PASSWORD=yes
networks:
- myweb
networks:
myweb:
name: ${MY_WEB_NETWORK_NAME}
driver: bridge