Tiếp tục với series bài viết về Docker cho người mới bắt đầu, trong bài này mình sẽ giới thiệu về Docker-Compose, 1 công cụ cho phép kết nối các Container lại và làm cho chúng có thể tương tác với nhau.
Docker Hub
Trước khi tới với Docker-Compose thì mình sẽ giới thiệu qua chút về DockerHub nhé. DockerHub là nơi lưu trữ và chia sẻ các image của Docker, cũng hỗ trợ build image trên server, ...
Mình có tạo mới 1 repo docker-basic trên Github.
Và 1 repo mới tên là docker_basic trên DockerHub. Khuyến khích bạn nâng cấp lên tài khoản Pro để sử dụng tính năng tự động build image và tự động đẩy "built image" vào Docker repositories. Bạn cũng có thể sử dụng gói Free theo QuickStart. Ở đây mình sẽ minh họa theo gói Pro để đơn giản hơn cho mn.
Bước đầu, click Create chọn Create Automated Build, chọn Github rồi trỏ tới docker-basic bạn vừa tạo ở GitHub.
Sau khi tạo thành công, bạn vào tab Build Settings và chọn branch mong muốn, đặt tên tag phù hợp và nhấn Save Changes để hoàn thành
Nếu bây giờ bạn push code lên branch nào trên github, mà branch đó bạn đã setting trên DockerHub (đã được chọn như ảnh trên) thì Server DockerHub sẽ tiến hành build images 1 cách tự động. Theo ảnh trên, nếu mà mình merge nhánh init_dockerfile vào nhánh master thì images sẽ được tự động build tiếp trên master branch.
Bạn có thể theo dõi kết quả trong tab Build Detail.
Ok, vậy là mình đã giới thiệu qua DockerHub, nó sẽ giúp ích rẩt nhiều, mn hãy thử trước khi tiếp tục vào phần tiếp theo nhé.
Vì sao chúng ta cần Docker Compose ?
Quay trở lại với lý do mà chúng ta sử dụng Docker Composer, khi bắt đầu 1 dự án mới, ta muốn setup Docker cho nó, tất nhiên là sẽ sử dụng Dockerfile, cài đặt tất cả những môi trường cần thiết lên một container duy nhất, rồi chạy Project của chúng ta trên container đó. Có 1 hướng đi hay hơn trong việc xây dựng image, đó là dùng lại hay kết hợp các image có sẵn để không cần mất thời gian tạo lại từ đầu, ngoài ra nếu ta muốn nhiều Project cùng dùng chung 1 cơ sở dữ liệu thì làm như thế nào.
Khi đó, chúng ta sẽ xây dựng nhiều container, mỗi container sẽ làm 1 nhiệm vụ riêng, khi nào cần tương tác với database thì gọi tới container mysql chẳng hạn, tương tác với redis thì gọi tới container redis, cần cái gì thì gọi tới container làm nhiệm vụ đó.
Cài đặt
Mình sẽ giới thiệu về cách cài đặt trên Linux, còn với các hệ điều hành khác, các bạn tham khảo tại đây .
B1: Tải phiên bản Docker Compose mới nhất
sudo curl -L "https://github.com/docker/compose/releases/download/1.22.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
B2: Cấp quyền thực thi
sudo chmod +x /usr/local/bin/docker-compose
Bây giờ thì cùng kiểm tra lại kết quả cài đặt bằng lệnh
docker-compose --version
Vậy là xong !
Cấu hình
Ta sẽ tạo ra cấu trúc như dưới đây, gồm 3 file chính :
- docker / entrypoint.sh : Liệt kê những câu lệnh cần chạy sau khi bật container
- Dockerfile: trong bài trước mình có trinhf bày khá chi tiết rồi
- docker-compose.yml : Dùng để khai báo và điều phối hoạt động của các container trong project
Xây dựng các container
- mysql - container dùng để kết nối cơ sở dữ liệu
Vào DockerHub và tìm kiếm với từ khóa mysql như dưới đây
Chúng ta chú ý 1 chút đó là dưới tên mysql có cụm từ Docker Oficial Images, mang ý nghĩa nó là image chính thức được Docker cung cấp, chúng ta sẽ yên tâm hơn khi sử dụng, và khuyến cáo luôn là nên sử dụng ( ít có khả năng gặp bug hơn hoặc chèn mã độc). Bạn cũng có thể sử dụng những image khác, những images này là do cá nhân, tổ chức khác xây dựng, có thể sẽ có những cải tiến so với bản official tuy nhiên độ tin cậy và chính xác thì khó mà bằng bản chuẩn được.
- ruby - container dùng cho web application
Vậy là chúng ta đã xác định được 2 image tương ứng với 2 container sẽ được xây dựng.
Viết docker-compose
Để nắm được tổng quan về các services trong project, ta sẽ viết docker-compose.yml trước
version: '3.5' // phiên bản của docker-compose
services: // liệt kê các services
mysql:
image: mysql:5.7 // chỉ định image để khởi động container
container_name: mysql // tên container có thể tùy chỉnh
restart: always // container sẽ khởi động lại nếu mã thoát cho biết lỗi không thành công, mặc định là no
environment: // các biến môi trường
MYSQL_ROOT_PASSWORD: root
volumes: // Chia sẻ dữ liệu giữa container (máy ảo) và host (máy thật) hoặc giữa các container với nhau
- docker/database:/var/lib/mysql
app:
container_name: app
build: . // Sử dụng khi chúng ta không xây dựng container từ image có sẵn nữa mà xây dựng nó từ Dockerfile
volumes:
- .:/my_app
ports: // Cấu hình cổng kết nối
- "3000:3000"
environment:
DATABASE_HOST: mysql // tên của service mysql
DATABASE_USER_NAME: root
DATABASE_PASSWORD: root
Chi tiết hơn 1 chút với :
- volumes : Khi container mysql tạo và lưu dữ liệu thì dữ liệu này sẽ lưu ở trong thư mục var/lib/mysql của container. Như vậy nếu như container này bị xóa đi thì chúng ta sẽ mất toàn bộ data. Vậy nên chúng ta sẽ sao lưu dữ liệu đó ra ngoài máy host, như vậy khi container bị xóa, dữ liệu sẽ vẫn được lưu trữ ở máy host. Và ở khi bật lại container, dữ liệu lại được mount từ máy host vào trong container và chúng ta tiếp tục sử dụng nó bình thường. Thư mục lưu trữ data ở ngoài máy host sẽ không được commit vào git, ta đưa nó vào gitignore.
- build : Nếu Dockerfile nằm cùng thư mục với docker-compose.yml thì chỉ cần
build: .
Nếu bạn muốn đặt Dockerfile trong thư mục docker để cùng với entrypoint.sh cho gọn thì sửa thành
build:
context: ./
dockerfile: docker/Dockerfile
- ports : Có thể chỉ định cả 2 cổng (HOST:CONTAINER) tương ứng với (cổng ở máy thật: cổng ở máy ảo) hoặc chỉ định mình cổng cho máy ảo thôi.
Ví dụ: "1111:2222" Khi bạn truy cập vào cổng 1111 ở máy thật thì sẽ được ánh xạ tới cổng 2222 của máy ảo.
Viết Dockerfile
FROM ruby:2.5.1 // image cơ sở
MAINTAINER KienPH<thanhcong@gmail.com> // tác giả
RUN apt-get update && \
apt-get install -y nodejs // cài đặt nodejs
ENV TZ=Asia/Ho_Chi_Minh // cài đặt timezone cho máy ảo
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV APP_PATH /my_app
WORKDIR $APP_PATH // chỉ định thư mục làm việc mặc định (optional)
COPY Gemfile Gemfile.lock $APP_PATH/ // cài đặt framwork cần thiết cho dự án
RUN bundle install --without production --retry 2 \
--jobs `expr $(cat /proc/cpuinfo | grep -c "cpu cores") - 1`
COPY . $APP_PATH // Copy tất cả dữ liệu tự máy host vào trong container
COPY docker/entrypoint.sh /usr/bin/ // cấu hình file entrypoint.sh
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"] // thiết lập câu lệnh mặc định sẽ chạy khi khởi động container
Viết entrypoint.sh
#!/bin/bash
set -e
# Remove a potentially pre-existing server.pid for Rails.
rm -f /my_app/tmp/pids/server.pid
# Then exec the container's main process use CMD (what"s set as CMD in the Dockerfile).
exec "$@"
Tạo thêm file Gemfile và Gemfile.lock
Nội dung của file Gemfile khai báo nguồn và phiên bản của framework muốn sử dụng, file này khi chạy container sẽ được copy từ máy host vào container
source "https://rubygems.org"
gem "rails", "~>5"
file Gemfile.lock ta sẽ để trống
# This file is empty
Sử dụng docker-compose như thế nào ?
Khi tạo mới project với Rails trực tiếp trên máy thật, ta sẽ chạy lệnh :
rails new . --force --no-deps --database=mysql
Khi setup project mới thông qua Docker ta chỉ cần run một container đã cài Rails lên và chạy câu lệnh trên bên trong container đó, rồi mount các file, folder của framework được vừa được tạo ra ngoài máy host.
Cú pháp để chạy một câu lệnh bên trong container như sau:
sudo docker-compose run + tên container + Câu lệnh muốn chạy
Ví dụ :
docker-compose run app rails new . --force --no-deps --database=mysql
Trong đó app là tên container mà ta đã viết trong docker-compose.yml
Cấp lại quyền cho file, folder
Mặc định thì docker chạy với user root nên những file, folder nó tạo ra (sau khi mount từ container ra máy host) cũng ở quyền root. Vậy nên khi bạn dùng editor để chỉnh sửa những file này thì Linux OS sẽ thông báo.
Permission denied
Lúc này ta cần cấp lại quyền cho nó
sudo chown -R $USER:$USER .
Cấu hình để kết nối tới database
Đối với Rails framework thì config này nằm ở file config/database.yml
Sửa từ
default: &default
adapter: mysql2
encoding: utf8
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
password:
host: localhost
thành
default: &default
adapter: mysql2
encoding: utf8
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: <%= ENV.fetch("DATABASE_USER_NAME") || "root" %>
password: <%= ENV.fetch("DATABASE_PASSWORD") || "root" %>
host: <%= ENV.fetch("DATABASE_HOST") || "mysql" %>
Run
sudo docker-compose build // Build các image cần thiết
sudo docker-compose up // Khởi chạy container
Sau đó vào trình duyệt và paste vào url :
http://localhost:3000
==> Vâng, lỗi này là do chúng ta chưa tạo database
Ta cần mở thêm 1 tab khác và chạy :
sudo docker-compose run app rails db:migrate:reset
để xóa và tạo lại các bảng và quan hệ giữa chúng.
Bây giờ quay lại trình duyệt và reload lại trang
Tạm kết
Vậy là qua bài viết này, các bạn cũng đã sơ bộ nắm được 1 cách tổng quan về Docker Compose, cách setup 1 project Ruby on Rails thông qua Docker Compose, trong thời gian tới, nếu có thể, mình sẽ tiếp tục viết thêm các bài liên quan ( mình sẽ để link ở phía dưới ), cám ơn mn đã quan tâm.