Viblo Learning
+3

Bây giờ thì tôi đã biết đôi chút về Docker :D

1. Nguồn

Now I understand a little bit how docker works

2. Động cơ

Hiểu biết về những kiến thức mới và các trào lưu trong giới lập trình luôn là những điều hứng thú với bất kỳ developer nào, mình cũng không ngoại lệ. Mặc dù bây giờ mới tìm hiểu về docker có thể bị coi là quá muộn, tuy nhiên muộn còn hơn không bao giờ làm. Ngoài ra mình cũng đang tham gia 1 dự án sử dụng Docker cho nên lại càng có động lực để tìm hiểu, trong quá trình tìm tòi thấy có bài viết này khá hay và là lời hướng dẫn nhẹ nhàng để các bạn có hứng thú với Docker và áp dụng dần vào thực tiễn.

3. Từ khoá

  • Docker : Công nhân bốc xếp (tratu.soha.vn)
  • Container : Thùng hàng (tratu.soha.vn)
  • Instance : Phiên bản
  • Host : Máy chủ
  • Dependancy: Phần phụ thuộc
  • map : ghép
  • root : gốc
  • Gemfile : Được dùng bởi một chương trình tiện ích (tên Bundler), nó sẽ “thêm” các gems này vào môi trường Rails (thật ra là nó download và đặt các gems này vào một nơi được Rails hiểu. Từ đó, Rails gọi các gems này ra, tích hợp vào ứng dụng).

Mường tượng ra ngoài đời sống, đặt vào trường hợp bạn có rất nhiều thứ muốn chuyển cho người khác qua đường hàng thuỷ. Tất nhiên để tránh lưu lạc cũng như tiết kiệm chi phí, bạn sẽ cần đóng vào các thùng hàng (container), rồi sau đó đưa cho bên di chuyển hàng hộ bạn. Ở đây, các công nhân bốc xếp (docker) sẽ đưa các thùng hàng (container) này lên thuyền rồi sau đó sẽ đưa tới đúng địa chỉ. Và tất nhiên rồi, thùng hàng (container) lúc được giao sẽ ở trạng thái y hệt như khi bạn gửi và không tốn công chuẩn bị lại thứ gì cả trước khi được sử dụng.

4. Bài viết

Vậy là tôi cũng đã nghiên cứu Docker được vài giờ rồi, quá muộn cho buổi party 😄. Tuy nhiên thật may mắn vì nó không giống với một vài frontend framework nào đó bị thay thế ngay sau vài tuần - hoặc là tôi mong như thế.

Mặc dù có điều đáng buồn rằng mặc dù tôi đang nói về docker và những thứ ở backend, tôi vẫn phải đề cập một chút về JS Framework, nhưng các bạn cứ yên tâm, bài viết này là về Docker.

Cách đây khá lâu, tôi đã làm một việc mà nhiều người cũng hay làm hồi đó - Tiến hành cài docker, rồi sau đó tự nhủ với bản thân "Thế là xong!" rồi chẳng bao giờ đụng tới nó nữa. Tôi cũng đã hiểu được chút ý tưởng chính mà Docker sẽ mang lại, tuy nhiên lúc đó còn quá nhiều thứ để học cho nên cũng chỉ dừng lại ở mức đó mà thôi.

Tại thời điểm đó nó vẫn chưa phải là thứ cần thiết cho tôi. Tôi vẫn luôn cảm thấy hạnh phúc và thoải mái với công việc của mình, cho đến cái ngày định mệnh 2 tuần trước khi tôi vướng vài vấn đề với dự án Rails đang upgrade. Nói rõ ra thì tôi có một vài vấn đề library quá cũ hoặc quá mới, chính vì thế tôi cần một phiên bản ruby khác - dẫn tới phải chỉnh file rbenv để nhận biết folder nào cần phiên bản ruby ...kiểu kiểu như vậy.

Tôi bắt đầu nghĩ rằng tại sao mình lại phải có toàn bộ đống library và ruby này trong máy của mình nhỉ? Có quá nhiều thứ thay đổi. Cứ mỗi lần tôi có vấn đề với dự án là lại phải sắp xếp lại hằng tỷ thứ ở trong đầu rồi cố nghĩ ra nguyên nhân nằm ở đâu.

  • Vấn đề ở rben?
  • Hay là homebrew nhỉ?
  • Hay là library của hệ thống?
  • Cũng có thể là thứ gì khác..?

Nếu có thể tôi muốn tất cả những thứ trong dự án được đóng gói lại để mọi thứ đều ngang bằng nhau, khi sinh ra vấn đề gì đó tôi sẽ biết ngay nó nằm ở đâu.

Điều này đã giúp tôi nhớ về lời hứa của Docker - Linux container.

4.1 Docker với dev rails

Ý tưởng của tôi chính là - tôi muốn tạo được một container cho những dự án mà mình tham gia. Host - Máy chủ (OS X) của tôi không phải install thứ gì cả - mọi thứ đều nằm trong container. Điều đó có nghĩa là không có postgres trên OSX, không Redis, không Rubies, không có gì cả 😄

Nếu một dự án đòi hỏi library hay service gì đặc biệt, những thứ này sẽ được đặt trong Linux container cho dự án đó. Vì vậy, khi tôi "mở" một container, mọi thứ sẽ được chạy và quản lý bởi docker. Khi tôi đã xong với dự án đó, mọi thứ sẽ được tắt để giải phóng bộ nhớ cho những thứ khác. Quá tuyệt đúng không nào!

Ngoài ra tôi cũng muốn giữ những dự án này trên OS X để tôi có thể sửa đổi chúng bằng editor mà tôi yêu thích cũng như tìm những dự án như các folder thường. Tôi không muốn phải build lại container mỗi lần tôi có sửa đổi gì trong dự án. Điều đó sẽ làm cho toàn bộ dự án này trở nên vô nghĩa.

May mắn thôi, Docker cung cấp thứ gọi là Volume - cho phép những folder mount (lắp/đặt) trên container được link tới folder của host - nơi chứa dự án Rails của tôi. Mặc dù bây giờ có vẻ hơi khó hiểu, nhưng đừng lo các bạn sẽ nắm được nó khi đọc hết bài này.

Cuối cùng, tôi muốn đóng gói toàn bộ phần phụ thuộc (dependancy) của dự án vào trong một linux container trong lúc vẫn giữ được luồng làm việc phát triển được như trước.

Đây là cách tôi đã làm.

4.2 Dockerfile

Cách Docker làm việc chính là nó lấy một folder rồi tạo (build) một linux instance với folder đó. Dockerfile chính là Gemfile của Docker - thứ để bạn truyền lời với nó là build cái gì và như thế nào. File này cũng nói cho Docker biết là cần copy file gì từ folder này tới linux container. Ở trạng thái mặc định, Docker sẽ copy toàn bộ mọi thứ có trong linux container. Đây chính là lý do tại sao nó lại rất dễ để di chuyển và bạn có thể deploy nó tại mọi nơi, bởi vì mọi thứ nó cần chạy đều được copy ở sẵn trong image. Nhưng đó không phải là thứ tôi muốn. Tôi là người phát triển - điều duy nhất mà tôi muốn image của mình lưu giữ chính là các phần phụ thuộc mà tôi cần có để dự án rails của mình chạy được.

Điều đó có nghĩa tôi muốn cài library phát triển postgres, nodejs cho Javascript runtime để Rails chạy, XML và tất nhiên là môi trường Ruby nữa. Đó chính là những thứ tôi không muốn có trên OS X của mình nhưng lại cần cho dự án.

Dựa theo tất cả những điều này, dưới đây chính là Dockerfile nằm ở gốc(root) của dự án rails.

Dockerfile

FROM ruby:2.2

RUN apt-get update -qq && apt-get install -y build-essential

RUN apt-get install -y libpq-dev
RUN apt-get install -y libxml2-dev libxslt1-dev
RUN apt-get install -y nodejs

WORKDIR /rails

Dòng đầu tiên thể hiện việc nói với Docker hãy build một container với thứ đã có sẵn: Ruby 2.2. Điều này có nghĩa container sẽ có ngay ruby 2.2 được chạy và tinh chỉnh rồi. Chúng ta không cần phải tự cài ruby nữa.

Dòng hướng dẫn RUN chính là để đảm bảo linux container của tôi được mới nhất và toàn bộ những library cần phải có cho môi trường dự án Rails cũng sẽ được cài. Ở đây chỉ có dòng bao gồm chỗ libpg-dev là bạn nên thay đổi nếu muốn dùng MySQL thay vì Postgresql. Mọi thứ khác đều là những thứ rất thông dụng trong việc cài môi trường cho rails.

Hãy chú đoạn hướng dẫn WORKDIR. Câu lệnh này sẽ cài đặt địa chỉ làm việc hiện tại. Bạn chắc hẳn sẽ nhận ra /rails là một folder chưa có trong cấu trúc file của linux tiêu chuẩn, điều đó đúng đấy, nó chưa tồn tại đâu.

Còn lạ đời hơn, image này hoàn toàn rỗng. Nếu bạn build image này và tìm hiểu thử về nó, bạn sẽ nhận ra linux container của bạn chưa bao gồm dự án Rails của bạn. Điều này chính là vì Dockerfile copy file tới container. Tôi không muốn copy, tôi muốn thứ như symlink hay đại loại như vậy.

Bạn có nhớ câu chuyện về Volume và cách mà nó làm mọi thứ trở nên có thể không? Dưới đây là cách để bạn tạo ra các volume này.

4.3 Docker-compose để bao gồm dự án của bạn và quản lý bundle

Docker-compose là một tiện ích nhỏ - có dạng một YAML file và ghép nó vào command line. Chính vì vậy, thay vì việc phải gõ các câu command line phức tạp, bạn chỉ cần chỉ định những yêu cầu vào file YAML đó rồi để docker-compose tạo ra những thứ đó cho bạn. Điều này sẽ giúp bạn dễ dàng hơn trong việc chia sẻ những yêu cầu tinh chỉnh (configuration) và đưa container vào hoạt động nhanh chóng hơn.

Docker-compose cần một file tên là docker-compose.yml tại gốc của folder, bên cạnh Dockerfile.

Tại môi trường hiện nay, tôi muốn docker-compose quản lý một vài thứ như:

  • Chạy postgres container
  • Cài đặt môi trường cho Bundler
  • Ghép (map) port
  • Ghép dự án của tôi từ OSX tới container
  • Liên kết (link) với postgres để Rails có thể kết nối tới nó. Docker-compose có thể quản lý nhiều container trong cùng 1 file. Những yếu tố (element) tại gốc (root) chính là các container. Ở cài đặt của tôi hiện nay, dbrails chính là 2 container tách biệt.

docker-compose.yml

db:
  image: postgres
  ports:
    - "5432"
rails:
  build: .
  command: bundle exec rails server -p 3000 -b 0.0.0.0
  environment:
    BUNDLE_PATH: /bundle
    RACK_ENV: development
    RAILS_ENV: development
  ports:
    - "3000:3000"
  volumes:
    - .bundle:/bundle
    - .:/rails
  links:
    - db

Container có tên rails được liên kết (link) tới db. Điều này là rất quan trọng vì tôi muốn rails có thể kết nối được tới database để create/migrate/destroy database khi tôi muốn phát triển những chức năng mới. (db host được cài đặt bởi docker-compose, các bạn có thể vào container rails, mở file /etc/hosts sẽ thấy nó tạo liên kết từ rails tới db.)

Tôi rất thích đoạn bundle path (đường dẫn bundle). Khi debug (sửa lỗi) chương trình tôi thường nhìn vào nội dung của các gem và tôi muốn nó có thể truy cập được từ phía bên ngoài của container. Bằng cách sử dụng một volume, các gem được bundle sẽ nằm trong root của dự án.

Các bạn còn nhớ quy tắc WORKDIR trong Dockerfile không? Bây giờ /rails đã được ghép (map) tới root của dự án.

Bây giờ thì tôi đã sẵn sàng để build docker image, và bundle install những gem cần thiết cho rails.

docker-compose build
docker-compose run rails bundle install

Bạn sẽ để ý thấy Bundler yêu cầu cần có quyền root. Hiện nay có một vấn đề trên Github chính là nó ngăn cản việc volume khi chỉ có quyền user thường. Chính vì vậy, hiện tại tôi phải chạy mọi thứ với quyền root. Cũng không phải là điều gì đó quá to tát.

Dù sao, image giờ đã được build xong và dự án cũng được bundle với các gem của nó, bây giờ là lúc để tinh chỉnh database.yml để kết nối được tới container.

database.yml

default: &default
  adapter: postgresql
  timeout: 5000
  encoding: unicode
  pool: 5
  username: postgres
  host: db

development:
  <<: default
  database: myapp_development

test:
  <<: default
  database: myapp_test

production:
  <<: *default
  database: myapp_production

Điều quan tọng nhất chính là bạn cần nhớ quy luật cho host, ở đây đang được set là db. Tại thời điểm này, tôi có thể tạo database của mình và chạy dự án.

docker-compose run rails rake db:create
docker-compose up

Bạn sẽ thấy những thứ như Heroku log nếu mọi thứ đều ổn.

example.log

Recreating myproject_db_1...
Recreating myproject_rails_1...
Attaching to myproject_db_1, myproject_rails_1
db_1    | LOG:  database system was shut down at 2015-06-12 13:59:52 UTC
db_1    | LOG:  database system is ready to accept connections
db_1    | LOG:  autovacuum launcher started
rails_1 | => Booting WEBrick
rails_1 | => Rails 4.2.1 application starting in development on http://0.0.0.0:3000
rails_1 | => Run `rails server -h` for more startup options
rails_1 | => Ctrl-C to shutdown server
rails_1 | [2015-06-12 14:00:16] INFO  WEBrick 1.3.1
rails_1 | [2015-06-12 14:00:16] INFO  ruby 2.2.2 (2015-04-13) [x86_64-linux]
rails_1 | [2015-06-12 14:00:16] INFO  WEBrick::HTTPServer#start: pid=1 port=3000

Nếu bạn đang dùng OSX, bạn sẽ cần dùng boot2docker ip để chỉ browser về đúng dự án của bạn.

boot2docker ip
-> 192.168.59.103

Tại trường hợp này, tôi sẽ chỉ browser về địa chỉ 192.168.59.103:3000 và nhìn thấy công sức của mình.

63%2FScreen+Shot+2015-06-12+at+10.02.22+AM.png

Lời cuối

Bài viết này đã được đưa lên vào tháng 6 năm 2015 (hơn 1 năm trước). Trong 1 năm này, docker đã có rất nhiều thay đổi với các chức năng mới giúp việc phát triển trở nên dễ dàng hơn bao giờ hết. Hy vọng các bạn sẽ hiểu qua được đôi chút về Docker và có hứng thú với nó, và từ các dự án sau áp dụng để giúp nâng cao hiệu suất và tính chuyên nghiệp khi làm dự án.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.