Hành trình cùng cá voi xanh - Dockerize app
Docker
29
Male avatar

manhdung viết ngày 28/07/2016

Mục đích

Docker hiện tại là một công nghệ không phải mới nhưng vẫn rất nổi hiện nay. Nhân hôm nay trời mưa bão, rảnh rỗi, mình sẽ thử dockerize một ứng dụng nho nhỏ gồm nginx làm proxy và python app.

Mô hình

Mô hình mình dựa theo tutorial này của Digital Ocean:
https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-uwsgi-and-nginx-on-ubuntu-14-04

Ngắn gọn thì:
nginx đóng vai trò reverse proxy, thông qua wsgi protocol giao tiếp với backend app là một python web viết bằng flask. Python web này chỉ làm mỗi nhiệm vụ trả lời mọi request bằng một response "Hello there"

Trong bài tut của Digital Ocean sử dụng virtualenv để tạo môi trường độc lâp giữa các python app nhưng do mình áp dụng docker vào nên sẽ không cần cài đặt virtualenv nữa.

Theo best practice, mỗi container của docker chỉ nên chứa một service. Do đó, chúng ta sẽ có hai container: một cho nginx reverse proxy, hai cho backend python flask app. Tất cả container này đều chạy chung một host.

Thư mục chứa code và các file config, mình host trên:
https://github.com/dungmanh88/system
trong thư mục app/simple_flask

Chuẩn bị Dockerfile

Dockerfile sẽ chứa các chỉ thị mà từ đó chúng ta build ra một docker image.

FROM centos:latest

RUN yum -y install epel-release \
        && yum -y install python34 python-pip python-devel gcc gcc-c++ \
    && pip install --upgrade pip \
    && pip install flask uwsgi

COPY ./ /simple_flask
WORKDIR /simple_flask

CMD ["uwsgi", "--ini", "wsgi.ini"]

FROM là keyword bắt buộc có, cho biết docker image này sẽ base trên mọt bản centos image. Tốt nhất bạn nên base trên official docker image. Nếu không đánh tag thì mặc định docker hiểu bạn dùng latest version. Nếu không có official docker image nào ưng ý trên docker hub thì bạn nên chọn các image có kèm Dockerfile để biết rõ tác gỉa cài đặt copy cái gì vào đâu trong image. Trong Dockerfile chỉ có duy nhất một chỉ thị FROM.

RUN là chỉ thị cho biết sẽ thực hiện lệnh nào khi build image. RUN thường dùng để chạy các command cài dependency packages. Vì mỗi một chỉ thị trong Dockerfile sẽ phát sinh một layer nên ở đây mình chain các command lại bằng && nhằm hạn chế bớt layer sẽ được tạo ra trong image khiến image nhỏ gọn hơn. Tuy vậy cách làm này lại không tận dụng được cơ chế cache của docker trong qúa trình build image. Mặc định, docker sẽ cache các layer lại nên khi re-build lại thì rất nhanh. Nhưng nếu chỉ có một command trong chuỗi command nối nhau kể trên bị thay đổi thì thì layer đó hiểu là bị thay đổi, docker sẽ chạy lại tất cả các command trong chỉ thị RUN và tất cả các chỉ thị ngay sau đó. Thực sự thì ít khi dependency packages của image thay đổi mà chỉ có phần file được copy vào mới hay thay đổi, nhưng phần này thì dung lượng rất nhỏ nên mình thấy các Dockerfile vẫn hay được viết theo kiểu chain command.

COPY là chỉ thị sẽ copy tất cả các file có trong thư mục hiện tại mà chứa Dockefile trên docker host ngoại trừ các file liệt kê trong .dockerignore Thư mục trên host chứa Dockerfile gọi là build context. Khi build toàn bộ các file/thư mục trong build context được send đến docker server. Nếu build context có một file nặng vài G mà không dùng đến trong image thì nó cũng bị send đến server khiến qúa trình build sẽ bị chậm lại ở bước send context. Đó là lý do một số người dùng .dockerignore để filter bớt các thư mục/file rác trong build context.

Ở đây, .dockerignore của mình gồm:

Dockerfile
README.md
simple_flask.conf

WORKDIR là chỉ thị cho biết khi chạy container thì thư mục nào là điểm khởi đầu

CMD là chỉ thị cho biết lệnh nào được chạy khi bật container. Do chỉ định WORKDIR rồi nên lệnh này sẽ được thực hiện tại /simple_flask trong container luôn. Lệnh này phải thực hiện ở chế độ foreground nếu không container sẽ bị thoát ra ngay lập tức. Để giữ cho container running thi có hai cách:

  • Một là chạy process ở foreground
  • Hai là chạy process ở background nhưng có tail -f log, dùng tail để giữ cho container tiếp tục running

Trong một Dockerfile chỉ có duy nhất một chỉ thị CMD

Chuẩn bị docker network

Như đã viết trong bài http://kipalog.com/posts/Cach-lien-ket-cac-container-lai-voi-nhau-trong-docker, mình sẽ cần một user-defined network để giúp các container nói chuyện với nhau dễ dàng.

Trên host đã cài sẵn docker engine, bạn chạy

docker network create my-net

Build image cho python app

Tại thư mục có chứa Dockerfile ( chính là build context ), bạn chạy

docker build -t xavivn/simple_flask .

Dấu chấm cuối để chỉ thư mục hiện tại là build context
Tên image thường đặt theo chuẩn:

<Tên cá nhân hoặc tổ chức>/<Tên dịch vụ>:<version>

Ở đây mình không khai báo version nên mặc định là latest

Run docker image xavivn/simple_flask

docker run -itd --net=my-net --name=simple_flask  xavivn/simple_flask:latest

Lý do phải dùng --name và --net mình đã viết trong http://kipalog.com/posts/Cach-lien-ket-cac-container-lai-voi-nhau-trong-docker

Python app flask này dùng port uwsgi/8000 nhưng port này chỉ cần thiết truy cập từ web proxy nên bạn không cần map port ở đây

Run docker image nginx

docker run -itd --net=my-net --name=nginx -p 80:80 -v /app/simple_flask/nginx/simple_flask.conf:/etc/nginx/conf.d/simple_flask.conf nginx

Do container này sẽ trực tiếp phục vụ client nên cần phải map port 80 trên host với port 80 trong container qua tham số -p

Config của nginx thường hay thay đổi nên mình mount nó ra một file bên ngoài nằm trên host. File /etc/nginx/conf.d/simple_flask.conf trong container sẽ tham chiếu đến file /app/simple_flask/nginx/simple_flask.conf trên host.

Nói chung những file/thư mục nào thường thay đổi thì bạn nên mount ra ngoài. Trừ trường hợp code của web thì nên đóng gói luôn vào container để tiện vận chuyển và dễ dàng deploy ( áp dụng trong CI/CD, docker thường được dùng làm phương tiện vận chuyển môi trường do tính tinh gọn), dễ dàng scale. Khi cần thay đổi code ngay lập tức thì bạn có thể git pull về docker host rồi dùng docker cp ghi đè thư mục code trong container. Trong trường hợp backend db thì khó hơn chút vì datadir khá lớn, nhỏ cũng tầm 20-30G. Lượng data đó qúa lớn nhét vào container thì không được, build vừa lâu lại vừa khó vận chuyển phân phối. Cũng vì lý do này áp dụng docker ở tầng db rất hạn chế, việc scale db vẫn không khác gì so với trước đây. Nếu muốn áp dụng docker tầng db thì phần datadir cần được tách rời và host bằng công nghệ storage như glusterfs, ceph chẳng hạn nhưng giải pháp cho bọn này đòi hỏi nâng cấp hạ tầng network để đảm bảo performance. Cá nhân mình thấy docker áp dụng tốt để làm container cho tầng app, web thôi. Không có giải pháp nào bao trùm hết vấn đề.

Nội dung file này như sau:

upstream simple_flask_app {
    server simple_flask:8000;
}

server {
    listen 80;
    server_name simple.flask.example.com;

    location / {
        include uwsgi_params;
        uwsgi_pass simple_flask_app;
    }
}

Chỉ là khai báo một vhost, container python app được tham chiếu qua tên container. Do file config tham chiếu đến container python app nên bạn phải chạy image xavivn/simple_flask trước.

Do các container dùng bridge network để giao tiếp, cơ chế đằng sau là NAT bằng iptables nên bạn chú ý không stop service iptables, các rules iptables sẽ được docker tự động thêm vào. Các bạn có thể xem chi tiết ở: http://kipalog.com/posts/Tim-hieu-bridge-network-trong-Docker

Test

Từ một client, cấu hình /etc/hosts simple.flask.example.com trỏ đến IP của docker host, sau đó gọi vào http://simple.flask.example.com/

alt text

Kết thúc bài viết. Hi vọng bài viết sẽ giúp ích một số bạn trong quá trình tìm hiểu và sử dụng docker.

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

Male avatar

manhdung

44 bài viết.
240 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
Male avatar
67 11
Giới thiệu RabbitMQ là một message broker ( messageoriented middleware) sử dụng giao thức AMQP Advanced Message Queue Protocol (Đây là giao thức ph...
manhdung viết gần 3 năm trước
67 11
Male avatar
45 4
Giả định bạn tiếp nhận một server mới toanh, bạn cần tìm một số thông tin về nó như loại CPU, loại main, loại memory, memory dùng của hãng nào... c...
manhdung viết hơn 1 năm trước
45 4
Male avatar
38 7
Giới thiệu MongoDB là một giải pháp nosql database. Data được lưu ở dạng các bson document. Hỗ trợ vertical scaling và horizontal scaling, dynamic...
manhdung viết hơn 2 năm trước
38 7
Bài viết liên quan
White
8 0
Một trong những trường hợp build docker image là thừa hưởng từ một image mẹ Lấy một ví dụ sau: Dockefile FROM my_repo/my_image ENTRYPOINT ech...
Lơi Rệ viết hơn 2 năm trước
8 0
White
16 0
Công cụ này dành riêng cho những người lười muốn muốn tiết kiệm thời gian thao tác với docker bằng dòng lệnh với các lợi ích sau: + Tiết kiệm thời...
Phí Ngọc Chi viết hơn 1 năm trước
16 0
White
3 5
Docker là một trong những giải pháp đóng gói và cài đặt có xu hướng phát triển mạnh hiện nay. Tôi đã có vài lần giới thiệu và seminar về khả năng c...
Duyệt viết 2 năm trước
3 5
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


Male avatar
{{userFollowed ? 'Following' : 'Follow'}}
44 bài viết.
240 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!