Running Ruby on Rails with Docker Compose

สวัสดีครับทุกท่าน ในบทความนี้เราจะมาสร้าง Docker-Compose สำหรับ Ruby On Rails กันนะครับ เชื่อว่า Developer ส่วนใหญ่น่าพอจะรู้จัก Docker กันมั่งแล้ว ซึ่งเป็นสิ่งที่เข้ามาช่วยเราแก้ปัญหาในเรื่องของ Environment ในการพัฒนาซอฟต์แวร์ ทำให้ทุกคนในทีมสามารถพัฒนาซอฟต์แวร์บนแพลตฟอร์มที่เหมือนกันได้ หรือ สมาชิกใหม่ในทีมสามารถเริ่มรันโปรเจ็กได้โดยไม่ต้องลง Dependency ต่าง ๆ เพียงมีแค่ Docker และ Docker-Compose เท่านั้น
โดยตัวผู้เขียนเองได้มีโอกาสร่วมทำงานกับทีมที่ใช้ Ruby on Rails Framework ในการพัฒนา ซึ่งโดยปกติแล้วสิ่งที่จำเป็นต้องลงก่อนเริ่มพัฒนาจะมีดังนี้
-
Ruby
-
Postgres SQL
-
Redis
-
Yarn
อาจจะไม่ได้มากมายอะไร แต่... ตัวผู้เขียนเองไม่ได้พัฒนาเพียงแค่โปรเจ็กต์เดียว ซึ่งแต่ละโปรเจ็กต์ก็จะใช้ Dependency คนละเวอร์ชันกัน
ยกตัวอย่างเช่น
-
Project A ใช้ Ruby v2.5, Postgres v10 และ Redis v4
-
Project B ใช้ Ruby v2.6, Postgres v11 และ Redis v5
Docker compose จึงเข้ามามีบทบาทตรงนี้ โดยที่เราจะสร้าง docker-compose.yml ให้แต่ละโปรเจ็ก เพื่อที่เราจะได้ไม่ต้องคอยสลับเวอร์ชั่นซอฟต์แวร์ไปมา ซึ่งบางคนอาจมีปัญหาในการเปลี่ยนแปลงเวอร์ชั่นซอฟต์แวร์บางตัวรวมถึงตัวผู้เขียนเองเช่นกัน
เราจะสร้าง Service หลักๆขึ้นมา 4 ตัวดังนี้
-
Rails
-
Postgres
-
Redis
-
Sidekiq
แต่ในไฟล์ docker-compose.yml นั้น จะไม่ได้มีเพียงแค่ Services 4 ตัวนี้เท่านั้น แล้วจะมีอะไรบ้าง? มาดูกันเลย!
ตัวแรก
version: '3.4'
services: app: &app build: context: . dockerfile: ./.dockerdev/Dockerfile args: RUBY_VERSION: '2.6.3' PG_MAJOR: '11' NODE_MAJOR: '11' YARN_VERSION: '1.13.0' BUNDLER_VERSION: '2.2.2' image: project-a:1.0.0 tmpfs: - /tmp
Service: app ตัวนี้จะทำหน้าที่ในการเก็บตัวแปรเวอร์ชั่นของ Dependency ที่เราจะใช้ในการสร้าง Image ตัวแปลพวกนี้ก็จะถูกเรียกใช้โดย Dockerfile ในขั้นตอนการ Build image
backend: &backend <<: *app volumes: - ./project-a:/app - rails_cache:/app/tmp/cache - bundle:/bundle - node_modules:/app/node_modules - packs:/app/public/packs - .dockerdev/.psqlrc:/root/.psqlrc:ro environment: - NODE_ENV=development - RAILS_ENV=${RAILS_ENV:-development} - REDIS_URL=redis://redis:6379/ - DATABASE_URL=postgres://postgres:postgres@postgres:5432 - BOOTSNAP_CACHE_DIR=/bundle/bootsnap - WEBPACKER_DEV_SERVER_HOST=webpacker - WEB_CONCURRENCY=1 - HISTFILE=/app/log/.bash_history - PSQL_HISTFILE=/app/log/.psql_history - EDITOR=nano depends_on: - postgres - redis
Service: backend ตัวนี้ก็ยังไม่ใช่ service หลักที่เราจะรันเหมือนกันครับ แต่ถือว่าเป็น base service ที่เอาไว้ให้ service ตัวอื่น extends ไปใข้งานได้
อธิบายเพิ่มเติม:
<<: *app หมายถึงเรา extends ออกมาจาก services ที่ชื่อ app ที่ระบุไว้ก่อนหน้านี้
ในส่วนของ volumes จะเห็นว่ามีสองแบบ
ได้แก่ ./project-a:/app และ bundle:/bundle
- ./project-a:/app คือ การ mount ไฟล์จากเครื่องเราเข้าไปใน container สังเกตุที่ source path (./project-a) เป็นแบบ absolute path
- bundle:/bundle คือ การสร้าง container volume สังเกตุที่ source path จะเป็นชื่อ volume ไปเลย ในส่วนนี้เดี๋ยวจะมีอธิบายเพิ่มเติมนะครับ
ในส่วนของ depends_on ได้อ้างอิงไปถึงอีกสอง service (ที่กำลังจะพูดถึง) นั่นหมายถึงหว่า เมื่อเรารัน service ตัวนี้ ตัวที่อยู่ภายใต้ depends_on ก็จะรันขึ้นมาด้วย
และแล้ว... ก็มาถึง services ตัวหลักๆของเรา
rails: <<: *backend command: bundle exec rails server -b 0.0.0.0 ports: - '3000:3000' sidekiq: <<: *backend command: bundle exec sidekiq -C config/sidekiq.yml postgres: image: postgres:11.1 volumes: - .psqlrc:/root/.psqlrc:ro - postgres:/var/lib/postgresql/data - ./log:/root/log:cached environment: - PSQL_HISTFILE=/root/log/.psql_history ports: - 5432 redis: image: redis:4-alpine volumes: - redis:/data ports: - 6379
จะเห็นว่า rails และ sidekiq ได้ทำการ extends backend เหมือนกัน เพราะว่าจำเป็นต้องใข้ volume และ environment เดียวกัน สาเหตุที่แยก service rails กับ sidekiq ก็เพื่อความง่ายในการเรียกใช้งานและจริง ๆ แล้วเราอาจไม่จำเป็นต้องใช้งาน sidekiq ตลอด ตามมาด้วย service หลักอีกสองตัวคือ postgres และ redis สองตัวนี้ก็สร้างแบบปกติทั่วครับ
แต่ยังไม่หมดเพียงเท่านี้ เรายังขาดอีกส่วนที่จำเป็นไป นั่นก็คือ ...
volumes: postgres: redis: bundle: node_modules: rails_cache: packs:
การสร้าง volumes ให้กับ container นั่นเอง ซึ่ง volumes เหล่านี้สามารถใช้ร่วมกันในแต่ละ service ได้ เท่านี้เราก็จะได้ docker-compose.yml มาแล้ว มาชมหน้าตาไฟล์แบบเต็ม ๆ กันครับ
version: '3.4'
services: app: &app build: context: . dockerfile: ./.dockerdev/Dockerfile args: RUBY_VERSION: '2.6.3' PG_MAJOR: '11' NODE_MAJOR: '11' YARN_VERSION: '1.13.0' BUNDLER_VERSION: '2.2.2' image: project-a:1.0.0 tmpfs: - /tmp backend: &backend <<: *app volumes: - ./project-a:/app - rails_cache:/app/tmp/cache - bundle:/bundle - node_modules:/app/node_modules - packs:/app/public/packs - .dockerdev/.psqlrc:/root/.psqlrc:ro environment: - NODE_ENV=development - RAILS_ENV=${RAILS_ENV:-development} - REDIS_URL=redis://redis:6379/ - DATABASE_URL=postgres://postgres:postgres@postgres:5432 - BOOTSNAP_CACHE_DIR=/bundle/bootsnap - WEBPACKER_DEV_SERVER_HOST=webpacker - WEB_CONCURRENCY=1 - HISTFILE=/app/log/.bash_history - PSQL_HISTFILE=/app/log/.psql_history - EDITOR=nano depends_on: - postgres - redis rails: <<: *backend command: bundle exec rails server -b 0.0.0.0 ports: - '3000:3000' sidekiq: <<: *backend command: bundle exec sidekiq -C config/sidekiq.yml postgres: image: postgres:11.1 volumes: - .psqlrc:/root/.psqlrc:ro - postgres:/var/lib/postgresql/data - ./log:/root/log:cached environment: - PSQL_HISTFILE=/root/log/.psql_history ports: - 5432 redis: image: redis:4-alpine volumes: - redis:/data ports: - 6379
volumes: postgres: redis: bundle: node_modules: rails_cache: packs:
วิธีใช้งาน
หากว่าเป็นครั้งแรกที่เริ่มใช้งาน เราก็ต้องทำการสร้าง image ขึ้นมาก่อนโดยการใช้คำสั่ง
docker-compose build
หลังจาก build เสร็จแล้ว เราก็จะได้ image ที่พร้อมใช้งาน (ถ้าตามตัวอย่าง image จะชื่อ project-a) ครวนี้เราก็จะทำการ migrate database โดยใช้คำสั่ง
docker-compose run rails rails db:schema:setup
"docker-compose run rails" หมายความว่าเราจะส่งคำสั่งเข้าไปใน service ที่ชื่อ rails โดยคำสั่งที่จะส่งไปก็คือ rails db:schema:setup
หลังจาก migrate database เรียบร้อยแล้ว web application ของเราก็พร้อมที่จะใช้งานแล้ว
เราจะสั่งรันเซิฟเวอร์ด้วยคำสั่ง
docker-compose up rails
เมื่อรันคำสั่งดังกล่าว docker ก็จะรัน service ขึ้นมา 3 ตัว ได้แก่ postgres, redis และ rails
เท่านี้เราก็สามารถเช้าใช้งาน web application ได้แล้ว ผ่าน http://localhost:3000 แต่ ... ถ้าใช้ Docker Toolbox จะต้องใช้ IP ของ docker machine โดยสามารถดู IP ได้โดยใช้คำสั่ง docker-machine ip
และขอทิ้งท้ายด้วยสิ่งที่หลายๆ คนอาจสงสัยว่าทำไมไม่มี ... สิ่งนั้นมันหายไปไหน ?
ก็คือ .... Dockerfile นั่นเอง
ARG RUBY_VERSION
FROM ruby:$RUBY_VERSION
ARG PG_MAJOR
ARG NODE_MAJOR
ARG BUNDLER_VERSION
ARG YARN_VERSION# PostgreSQLUN curl -sSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && echo 'deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main' $PG_MAJOR > /etc/apt/sources.list.d/pgdg.list# NodeJS
RUN curl -sL https://deb.nodesource.com/setup_$NODE_MAJOR.x | bash -# Yarn
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && echo 'deb http://dl.yarnpkg.com/debian/ stable main' > /etc/apt/sources.list.d/yarn.list# Install dependencies
RUN printf 'nano' > /tmp/Aptfile
RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends build-essential postgresql-client-$PG_MAJOR nodejs ca-certificates yarn=$YARN_VERSION-1 $(cat /tmp/Aptfile | xargs) && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && truncate -s 0 /var/log/*log# Configure bundler and PATH
ENV LANG=C.UTF-8 GEM_HOME=/bundle BUNDLE_JOBS=4 BUNDLE_RETRY=3
ENV BUNDLE_PATH=$GEM_HOME
ENV BUNDLE_APP_CONFIG=$BUNDLE_PATH BUNDLE_BIN=$BUNDLE_PATH/bin
ENV PATH /app/bin:$BUNDLE_BIN:$PATH
RUN unset BUNDLE_PATH && unset BUNDLE_BIN# Update GEM and install Bundle
RUN gem update && gem install bundler:$BUNDLER_VERSION# Create a directory for the app code
RUN mkdir -p /app
WORKDIR /app
เนื่องจากมีเวลาจำกัด ดังนั้นในส่วนของ Dockerfile ผู้เขียนขออนุญาตอธิบายในบทความต่อไป หวังว่าบทความได้จะเป็นประโยชน์สำหรับผู้ที่ต้องการใช้งาน Ruby on Rails ด้วย Docker นะครับ และติดตามอ่านบทความดีๆ ที่น่าสนใจ ไม่ว่าจะเป็น Machine Learning, Startup, Design, Software Development และ Management ได้ทุกวันที่ Senna Labs Blog ครับ


Subscribe to follow product news, latest in technology, solutions, and updates
Other articles for you



Let’s build digital products that are simply awesome !
We will get back to you within 24 hours!Go to contact us








