29Jun, 2020
Language blog :
Thai
Share blog : 
29 June, 2020
Thai

Running Ruby on Rails with Docker Compose

By

5 mins read
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 ครับ

Written by
Senna Labs
Senna Labs

Subscribe to follow product news, latest in technology, solutions, and updates

- More than 120,000 people/day visit to read our blogs

Other articles for you

24
June, 2024
JS class syntax
24 June, 2024
JS class syntax
เชื่อว่าหลายๆคนที่เขียน javascript กันมา คงต้องเคยสงสัยกันบ้าง ว่า class ที่อยู่ใน js เนี่ย มันคืออะไร แล้วมันมีหน้าที่ต่างกับการประกาศ function อย่างไร? เรามารู้จักกับ class ให้มากขึ้นกันดีกว่า class เปรียบเสมือนกับ blueprint หรือแบบพิมพ์เขียว ที่สามารถนำไปสร้างเป็นสิ่งของ( object ) ตาม blueprint หรือแบบพิมพ์เขียว( class ) นั้นๆได้ โดยภายใน class

By

4 mins read
Thai
24
June, 2024
15 สิ่งที่ทุกธุรกิจต้องรู้เกี่ยวกับ 5G
24 June, 2024
15 สิ่งที่ทุกธุรกิจต้องรู้เกี่ยวกับ 5G
ผู้ให้บริการเครือข่ายในสหรัฐฯ ได้เปิดตัว 5G ในหลายรูปแบบ และเช่นเดียวกับผู้ให้บริการเครือข่ายในยุโรปหลายราย แต่… 5G มันคืออะไร และทำไมเราต้องให้ความสนใจ บทความนี้ได้รวบรวม 15 สิ่งที่ทุกธุรกิจต้องรู้เกี่ยวกับ 5G เพราะเราปฏิเสธไม่ได้เลยว่ามันกำลังจะถูกใช้งานอย่างกว้างขวางขึ้น 1. 5G หรือ Fifth-Generation คือยุคใหม่ของเทคโนโลยีเครือข่ายไร้สายที่จะมาแทนที่ระบบ 4G ที่เราใช้อยู่ในปัจจุบัน ซึ่งมันไม่ได้ถูกจำกัดแค่มือถือเท่านั้น แต่รวมถึงอุปกรณ์ทุกชนิดที่เชื่อมต่ออินเตอร์เน็ตได้ 2. 5G คือการพัฒนา 3 ส่วนที่สำคัญที่จะนำมาสู่การเชื่อมต่ออุปกรณ์ไร้สายต่างๆ ขยายช่องสัญญาณขนาดใหญ่ขึ้นเพื่อเพิ่มความเร็วในการเชื่อมต่อ การตอบสนองที่รวดเร็วขึ้นในระยะเวลาที่น้อยลง ความสามารถในการเชื่อมต่ออุปกรณ์มากกว่า 1 ในเวลาเดียวกัน 3. สัญญาณ 5G นั้นแตกต่างจากระบบ

By

4 mins read
Thai
24
June, 2024
จัดการ Array ด้วย Javascript (Clone Deep)
24 June, 2024
จัดการ Array ด้วย Javascript (Clone Deep)
ในปัจจุบันนี้ ปฏิเสธไม่ได้เลยว่าภาษาที่ถูกใช้ในการเขียนเว็บต่าง ๆ นั้น คงหนีไม่พ้นภาษา Javascript ซึ่งเป็นภาษาที่ถูกนำไปพัฒนาเป็น framework หรือ library ต่าง ๆ มากมาย ผู้พัฒนาหลายคนก็มีรูปแบบการเขียนภาษา Javascript ที่แตกต่างกัน เราเลยมีแนวทางการเขียนที่หลากหลาย มาแบ่งปันเพื่อน ๆ เกี่ยวกับการจัดการ Array ด้วยภาษา Javascript กัน เรามาดูตัวอย่างกันเลยดีกว่า โดยปกติแล้วการ copy ค่าจาก value type ธรรมดา สามารถเขียนได้ดังนี้

By

4 mins read
Thai

Let’s build digital products that are
simply awesome !

We will get back to you within 24 hours!Go to contact us
Please tell us your ideas.
- Senna Labsmake it happy
Contact ball
Contact us bg 2
Contact us bg 4
Contact us bg 1
Ball leftBall rightBall leftBall right
Sennalabs gray logo28/11 Soi Ruamrudee, Lumphini, Pathumwan, Bangkok 10330+66 62 389 4599hello@sennalabs.com© 2022 Senna Labs Co., Ltd.All rights reserved.