12/08/2018, 16:58

Tự xây dựng cho mình ứng dụng trên docker

Giới thiệu Docker là một công cụ tuyệt vời được thiết kể để tạo mới, chạy các tứng dụng bằng cách sử dụng các container. Các container cho phép đóng gói các ứng dụng với các thư viện, service, database... tất cả sẽ được đóng gói lại trong một container duy nhất. Nhờ vậỵ các developer sẽ chỉ cần ...

Giới thiệu

Docker là một công cụ tuyệt vời được thiết kể để tạo mới, chạy các tứng dụng bằng cách sử dụng các container. Các container cho phép đóng gói các ứng dụng với các thư viện, service, database... tất cả sẽ được đóng gói lại trong một container duy nhất. Nhờ vậỵ các developer sẽ chỉ cần tập trung vào việc viết code mà ko phải quan tâm đến việc cài cắm các service hay các ứng dụng thứ 3. Ở phần trước mình đã giới thiệu cho các bạn về các thao tác cơ bản để làm việc với docker. Trong phần này mình sẽ hướng dẫn các bạn tự xây dựng image cho riêng mình và kết các hợp các container để xây dựng ứng dụng rails .

Dockerfile

Định nghĩa: Dockerfile chứa tập hợp các lệnh để docker có thể đọc hiểu và thực hiện để đóng gói thành một image theo yêu cầu của người phát triển. Có thể hiểu Dockerfile là một file text chứa tất cả các câu lệnh để chỉ dẫn cho docker xây dựng images. Bằng cách sử dụng câu lệnh docker build docker sẽ tự động xây dựng theo các bước mà developer viết trên dockerfile. Format của Dockerfile :

# Comment
INSTRUCTION arguments

Phần tiếp theo là ý nghĩa của các chỉ dẫn trong Dockerfile. Hoặc bạn có thể tham khảo ý nghĩa của các câu lệnh và cách sử dụng tại đây

  • FROM : Là base image để chúng ta tiến hành build một image mới trên image. Vd: FROM ruby || ubuntu || mysql
  • MAINTAINER : thông tin của người phát triển Dockerfile
  • RUN : Sử dụng khi muốn thực thi các command trong quá trình xây dựng image
  • COPY : Copy một file từ host machine tới docker container
  • WORKDIR : image sẽ trỏ tới directory khai báo, nếu không có thì docker sẽ tự tạo mới
  • ENV : Định nghĩa các biến môi trường
  • CMD : Sử dụng khi muốn thực thi các command trong quá trình build container mới từ image

Sau đây là Dockerfile mình xây dựng với base image: ubuntu kết hợp với nginx

~/workspace/Docker/myUbuntu$ touch Dockerfile
~/workspace/Docker/myUbuntu$ ls
Dockerfile

Sau đó mình sẽ thêm vào Dockerfile nội dung như sau:

FROM ubuntu:16.04

CMD echo "Hello ubuntu"

# Update the repository
RUN apt-get update

# Install necessary tools
RUN apt-get install -y nano wget dialog net-tools vim git

# Download and Install Nginx
RUN apt-get install -y nginx

# Remove file index docker
RUN rm -v /var/www/html/index.nginx-debian.html

ADD index.nginx-debian.html /var/www/html

EXPOSE 80

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

Trước khi build mình sẽ thay đổi 1 chút trang index.html của thằng nginx bằng cách như sau:

~/workspace/Docker/myUbuntu$ ls
Dockerfile  index.nginx-debian.html  

Bạn có thể tạo tùy ý tạo trang index theo cách mà bạn mong muốn. Tiếp theo mình sẽ sử dụng các câu lệnh docker build -t "myubuntu:1.0" . (với tham số -t: khái báo tên và nhãn quả images, '.' để chỉ Dockerfile ở thư mục hiện tại đang trỏ tới) để build image với tên là myUbuntu.

~/workspace/Docker/myUbuntu$ docker build -t "myubuntu:1.0" .
Sending build context to Docker daemon 4.608 kB
Step 1/9 : FROM ubuntu:16.04
...
Successfully built 34eb6725fd74
~/workspace/Docker/myUbuntu$ docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
myubuntu             1.0                 34eb6725fd74        58 minutes ago      346 MB

Sau khi build xong image ta sẽ thực hiện tạo container từ image myubuntu vừa tạo được bằng câu lệnh sau

~/workspace/Docker/myUbuntu$ docker run -p 8080:80 -it myubuntu:1.0 /bin/bash

Sau khi vào được bash shell của container mình sẽ thực hiện start dịch vụ nginx bằng câu lệnh sau

root@56eefd1ad30c:/#  service nginx start
 * Starting nginx nginx

Kết quả sau khi chạy container:

Dockercompose

Docker compose là một công cụ dùng để quản lý và liên kết nhiều containers , mỗi container chạy 1 service riêng biệt nhưng phục vụ chung một ứng dụng. Docker-compose có định dạng giống file yaml (key - value), chứa các chỉ dẫn để khởi động, liên kết các container với nhau, sử dụng docker-compose cũng đơn giản chỉ với một vài câu lệnh đơn giản là có thể hiểu được. Ba bước cơ bản để sử dụng compose file:

  1. Xây dựng Dockerfile cho từng ứng dụng riêng biệt 2.Định nghĩa các service trong file docker-compose.yml để liên kết và chạy các ứng dụng cùng một thời điểm nhưng tách biệt môi trường 3.Sự dụng 2 câu lệnh sau để build và start docker-compose: docker-compose build và docker-compose up

Phần tiếp theo mình sẽ sử dụng docker-compose để liên kết 3 containers, mỗi container chạy 1 service như hình dưới gồm có nginx, rails và mysql server để tạo một ứng dụng hoàn chỉnh như sau: Tiếp theo là cấu trúc Dockkercompose trong thư mục. Ở bước đầu tiền mình sẽ chỉ liện kết 2 service là rails và db do đó mình sẽ commann phần nginx lại. Docker-compose

version: "2"
services:
  # web:
  #   build: ./nginx
  #   depends_on:
  #     - app
  #   links:
  #     - app
  #   ports:
  #     - 8080:80
  app:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/noteapp
    ports:
      - "3000:3000"
    depends_on:
      - db
    links:
      - db
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: 123456
    ports:
      - "3307:3306"

Mình xin được giải thích sơ qua về file docker compose sau:

  • version: để khai báo phiên bản định dạng mà mình thiết kế file docker-compose, ví thế tùy từng phiên bản mà các câu lệnh chỉ dẫn khác nhau(tham khảo tại đây)
  • services: chứa các container sẽ được khởi tạo và chạy
  • build: địa chỉ của dockerfile
  • command: khai báo lệnh sẽ được chạy khi start container
  • ports: port của hostmachine : port của container(bi) Tạo docker file cho rails app như sau. Dockerfile:
# Base image:
FROM ruby:2.3.3

# cài đặt các thư viện
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs

# tạo thư mục chứa ứng dụng
RUN mkdir /noteapp

WORKDIR /noteapp

COPY Gemfile /noteapp/Gemfile
COPY Gemfile.lock /noteapp/Gemfile.lock
RUN bundle install

COPY . /noteapp

Tạo Gemfile và chỉnh sửa nội dùng như sau:

source "https://rubygems.org"

gem "rails"

Cuối cùng là Gemfile.lock, sau khi bạn đã khởi tạo hoàn tất các file trên, tiếp theo ta sẽ thực hiện build dockerfile và tạo mới rails app bằng các câu lệnh sau

~/workspace/Docker/seminar-docker$ docker-compose build
# waiting to install
~/workspace/Docker/seminar-docker$ docker-compose run app rails new . --force --database=mysql --skip-bundle

Sau khi tạo mới project thì lúc này trong thư mục docker sẽ chứa các folder hoặc các file được sinh tự động của rails. Tuy nhiên ta vẫn phải cấp quyền chỉnh sửa trên thư mục như sau:

~/workspace/Docker/seminar-docker$ cd ..
~/workspace/Docker$ sudo chmod 777 -R seminar-docker/

Sau đó ta sẽ chỉnh sửa các file mặc định của thằng rails để ứng dụng có thể chạy được :

#config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root
  password: 123456
  host: db
  #socket: /var/run/mysqld/mysqld.sock

development:
  <<: *default
  database: noteapp_development

test:
  <<: *default
  database: noteapp_test

production:
  <<: *default
  database: noteapp_production
  username: noteapp
  password: <%= ENV['NOTEAPP_DATABASE_PASSWORD'] %>

Trong Gemfile mình sẽ sửa lại một chút như sau:

gem "rails", "~> 5.1.4"
gem "mysql2"

Sau khi sửa mình sẽ build lại và start toàn bộ container:

~/workspace/Docker/seminar-docker$ docker-compose build
~/workspace/Docker/seminar-docker$ docker-compose up

Ta có thể xem welcome page của Rails tại địa chỉ: 'localhost:3000'

Add Nginx’s container

Trước khi build mình muốn giải thích lí do tại sao lại chọn nginx để kết hợp với ứng dụng. Nginx là proxy server dùng để nhận các request từ phía client sau đó sẽ đưa tới rails server, tại đây thì hệ thống sẽ thực hiện các business logic và trả kết quả về cho client. Nginx đã giải quyết được nhiều vấn đề còn tồn đọng như C10k ngoài ra nginx có hỗ trỡ Load Balancing do đó nó trở thành một trong những webserver phổ biến hiện nay. Dưới đây là một cấu trúc phổ biến của thằng rails trên môi trường production. Quay lại phần trước, trong folder nginx được khởi tạo ở trước, ta sẽ tạo dockerfile cho nginx container :

# Base image:
FROM nginx
# Install dependencies
RUN apt-get update -qq && apt-get -y install apache2-utils
# establish where Nginx should look for files
ENV RAILS_ROOT /var/www/noteapp
# Set our working directory inside the image
WORKDIR $RAILS_ROOT
# create log directory
RUN mkdir log
# Copy Nginx config template
COPY nginx-app.conf /tmp/docker_example.nginx
# substitute variable references in the Nginx config template for real values from the environment
# put the final config in its place
RUN envsubst "$RAILS_ROOT" < /tmp/docker_example.nginx > /etc/nginx/conf.d/default.conf
# Use the "exec" form of CMD so Nginx shuts down gracefully on SIGTERM (i.e. `docker stop`)

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

Tiếp theo ta sẽ tạo file nginx-app.conf (nginx/nginx-app.conf) để thay đổi cho phù hợp với ứng dụng :

#It's used for proxying requests to other servers.
upstream puma_rails_app {
  #That app is a named resource that was specified in the docker-compose.yml file.
  server app:3000;
}
server {
  listen 80;
  proxy_buffers 64 16k;
  proxy_max_temp_file_size 1024m;
  proxy_connect_timeout 5s;
  proxy_send_timeout 10s;
  proxy_read_timeout 10s;
  location / {
    try_files $uri $uri/ @rails_app;
  }
  location @rails_app {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://puma_rails_app;
    # limit_req zone=one;
    access_log /var/www/noteapp/log/nginx.access.log;
    error_log /var/www/noteapp/log/nginx.error.log;
  }
}

Tiếp theo mình sẽ bỏ command của service web trong docker-compose và commad port của app :

version: "2"
services:
  web:
    build: ./nginx
    depends_on:
      - app
    links:
      - app
    ports:
      - 8080:80
  app:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/noteapp
    # ports:
    #   - "3000:3000"
    depends_on:
      - db
    links:
      - db
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: 123456
    ports:
      - "3307:3306"

Cuối cùng mình sẽ thực hiện build lại ứng dụng như sau:

~/workspace/Docker/seminar-docker$ docker-compose build
~/workspace/Docker/seminar-docker$ docker-compose up

Kiểm tra lại trang welcome của rails đã lên chưa ở địa chỉ localhost:8080 nhé

0