Cài đặt môi trường phát triển Rails với Docker

Mô tả

Trong series này mình sẽ từng bước xây dựng 1 ứng dụng Single Page Application với Rails API, ReactJS.

bài trước mình đã trình bày Ý tưởng, thiết kế Wireframe, Database và khởi tạo folder cho project 😎

Hôm nay mình sẽ hướng dẫn các bạn cách cài đặt môi trường phát triển với Docker

Công việc của chúng ta bao gồm các các phần chính:

  • Xây dựng các Docker Image
  • Kết hợp các containers với Docker Compose
  • Cấu hình NGINX
  • Chạy DEMO

Các bạn có thể xem lại bài trước để khởi tạo project RailsReactJS

Đây là những gì mình thêm vào để Docker nó:

docker
├── entrypoint
├── images
│   ├── db
│   │   └── Dockerfile
│   └── nginx
│       ├── Dockerfile
│       └── nginx.conf
└── wait-for-it.sh
Dockerfile
docker-compose.yml

Các bạn có thể xem chi tiết code tại https://github.com/oNguyenVanThinh/spa_tasks_api/pull/1/files

🎩 Bonus: Chia sẻ chút về kinh nghiệm tìm hiểu công nghệ của mình. Khi đọc một bài viết gì mình cũng có 3 bước:

  1. Đọc lướt qua một lượt xem có những phần gì, cố gắng hiểu một cách tổng quan
  2. Đọc kỹ hơn để hiểu từng chi tiết nhỏ
  3. Thực hành: Làm theo các bước hướng dẫn

Các bạn có thể áp dụng ngay cho việc đọc bài viết này !

Xây dựng các Docker image

Đầu tiên phải vào thư mục để code api với Rails mình đã tạo từ bài trước (Github)

$ cd spa_tasks_api/

Image: Rails App

Bắt đầu từ image Ruby 2.4 trên Docker Hub (https://hub.docker.com/_/ruby/ - đó chính là Ubuntu và đã cài đặt ruby 2.4)

Mình cài thêm Nodejs (để có yarn -> tính năng asset precompile của Rails) và Vim (editor để giúp chỉnh sửa file) Tạo 1 folder trong image là usr/src/app đây chính là folder mà chúng ta code Rails và copy các các file có trong project hiện tại vào image (trong đó có 2 file Gemfile, Gemfile) và sau đó dùng bundle để để cài các thư viện cần thiết.

Khi tạo container từ image Rails, nó sẽ chạy server lắng nghe ở cổng 3001 Tại sao mình chọn 3001 thay vì mình muốn để 3000 cho React App

# ./Dockerfile

FROM ruby:2.4

RUN apt-get update
RUN apt-get install -y build-essential nodejs --no-install-recommends
RUN apt-get install -y vim

RUN mkdir -p usr/src/app

WORKDIR usr/src/app

COPY Gemfile .
COPY Gemfile.lock .

RUN gem install bundler && bundle install

COPY . .

ENTRYPOINT ["bundle", "exec"]

CMD ["rails", "server", "-p", "3001", "-b", "0.0.0.0"]

EXPOSE 3001

Image: Postgresql

Mình dùng database là Postgresql vì vậy mình cũng cần phải build một image bắt đầu từ postgres được pull từ Docker Hub.

# ./docker/images/db/Dockerfile

FROM postgres

Image: Nginx

Đây là đoạn giới thiệu về NGINX

Tiếng Việt

Nginx là một máy chủ proxy ngược mã nguồn mở (open source reverse proxy server) sử dụng phổ biến giao thức HTTP, HTTPS, SMTP, POP3 và IMAP , cũng như dùng làm cân bằng tải (load balancer), HTTP cache và máy chủ web (web server). Dự án Nginx tập trung vào việc phục vụ số lượng kết nối đồng thời lớn (high concurrency), hiệu suất cao và sử dụng bộ nhớ thấp. Nginx được biết đến bởi sự ổn định cao, nhiều tính năng, cấu hình đơn giản và tiết kiệm tài nguyên.

Tiếng Anh

NGINX is open source software for web serving, reverse proxying, caching, load balancing, media streaming, and more. It started out as a web server designed for maximum performance and stability. In addition to its HTTP server capabilities, NGINX can also function as a proxy server for email (IMAP, POP3, and SMTP) and a reverse proxy and load balancer for HTTP, TCP, and UDP servers.

Nói thật mình cũng chẳng hiểu rõ NGINX lắm. Mình chỉ biết dùng nó lắng nghe ở cổng 80 (là cổng của giao thức HTTP, cổng mặc định khi duyệt web) và mình dùng nó để cấu hình các service xem cái nào hoạt động ở cổng nào. Trong bài này thì mình dùng nó để cấu hình proxy cho đường dẫn /api/ thì nó trỏ vào Rails server ở cổng 3001 và đường dẫn / thì trỏ vào Node server ở cổng 3000.

# ./docker/images/nginx/Dockerfile

FROM nginx

RUN mkdir -p /etc/nginx/conf.d/

COPY nginx.conf /etc/nginx/conf.d/default.conf

CMD ["nginx", "-g", "daemon off;"]

Đây là file cấu hình NGINX:

# ./docker/images/nginx/nginx.conf

upstream rails_app {
  server app:3001;
}

server {
  listen 80;
  keepalive_timeout 10;

  location / {
    proxy_pass http://rails_app;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
  }
}

Kết hợp các containers với Docker Compose

Sau khi đã có các Image cần thiết. Bước tiếp theo là chúng ta sẽ cần dùng docker-compose.yml để giúp các Image này có thể kết hợp với nhau và thành 1 sản phẩm. Có 3 service chính là db, app, nginx ở các cổng: 5432, 3001, 80

Mình dùng Docker Volume để giúp tạo ra các driver. Trong service db thì có db-data là external: false để giúp giữ lại được dữ liệu trong trường hợp container được tạo lại và service app thì có thông giữa code hiện tại của project với code trong image .:/usr/src/app để mỗi lần code có cập nhật thì không cần copy lại.

Khi chạy các service với nhau bằng docker-compose thì một internal network sẽ được tạo lập. Chúng ta có thể biết được địa chỉ IP của các service dễ dàng với linkdepends_on. Ở đây mình dùng depends_on. Và như thế container app connect db bằng IPdb. nginx biết Rails app có địa chỉ IPapp.

# ./docker-compose.yml

version: '2'

volumes:
  db-data:
    external: false

services:
  db:
    build: ./docker/images/db
    ports:
      - 5432:5432
    volumes:
      - db-data:/var/lib/psql/db-data
  app:
    build: .
    volumes:
      - .:/usr/src/app
    ports:
      - 3001:3001
    command: docker/wait-for-it.sh db:5432 -- docker/entrypoint
    depends_on:
      - db
  nginx:
    build: ./docker/images/nginx
    ports:
      - 80:80
    depends_on:
      - app

Vì khi chạy ứng dụng Rails ngoài việc chạy rails server , thì cần chạy nhiều lệnh khác như bundle để tải về các thư viện, rake:db:create trong trường hợp chưa có database, rake:db:migrate trong trường hợp thay đổi các bảng, vì thế mình tạo file entrypoint để lưu tất cả các lên đó, và dùng đoạn batch wait-for-it.sh để chạy các lệnh này.

Bạn có thể tải file wait-for-it.sh từ github của mình: https://github.com/oNguyenVanThinh/spa_tasks_api/blob/7009148d3e2a0e9c734a3f31105ffd20e5387414/docker/wait-for-it.sh

# ./docker/entrypoint

set -e
bundle check || bundle install
bundle exec rake db:create
bundle exec rake db:migrate
bundle exec puma -C config/puma.rb
exec "[email protected]"

Sau đó thêm vào file cấu hình database. (mình có thể dùng db thay cho địa chỉ ip của postgresql server là do tính năng link của docker-compose)

# ./config/database.yml

 default: &default
    adapter: postgresql
    encoding: unicode
 +  username: postgres
 +  host: db
 +  port: 5432

Chỉnh sửa file config của puma từ cổng 3000 -> 3001

# ./config/puma.rb

 + port        ENV.fetch("PORT") { 3001 }

Và đặc biệt, cần cấp quyền có thể chạy cho 2 file là wait-for-it.shentrypoint. Điều này rất quan trọng!. Vì nếu không cấp quyền nó sẽ không thể chạy.

sudo chmod +x docker/wait-for-it.sh
sudo chmod +x docker/entrypoint

Chạy demo

docker-compose build

docker-compose up

Giờ bạn có thể mở http://localhost:3001/ Hoặc sửa file /etc/hosts để vào bằng tên miền của bạn thích:

# ~/etc/hosts
127.0.0.1 localhost
127.0.0.1 spatasksapi

Vậy là mình có thể vào web bằng đường dẫn http://spatasksapi/ rồi 😃

$ docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                              NAMES
6603bc5367ea        spatasksapi_nginx   "nginx -g 'daemon of…"   19 minutes ago      Up 14 minutes       0.0.0.0:80->80/tcp                 spatasksapi_nginx_1
718b71fa4334        spatasksapi_app     "bundle exec docker/…"   19 minutes ago      Up 14 minutes       3000/tcp, 0.0.0.0:3001->3001/tcp   spatasksapi_app_1
edbbdb98e019        spatasksapi_db      "docker-entrypoint.s…"   28 minutes ago      Up 14 minutes       0.0.0.0:5432->5432/tcp             spatasksapi_db_1

Tóm lược

Bạn cũng có thể xem chi tiết code của bài viết này tại: https://github.com/oNguyenVanThinh/spa_tasks_api/pull/1/files Nếu thấy hữu ích thì nhớ upvote vào bên trái bài viết nhé 👋follow đến nhận thông báo khi có bài viết mới nhé ! Chúc các bạn tràn đầy năng lượng và làm việc hiệu quả 🕶


All Rights Reserved