+33

Sử dụng Docker (và cả Docker Compose) cho dự án Django

Bài viết gốc: https://manhhomienbienthuy.github.io/2016/04/10/su-dung-docker-va-docker-compose-cho-du-an-django.html (đã xin phép tác giả 😄)

Gần đây, khái niệm ảo hóa sử dụng container được nhắc đến khá nhiều. Và Docker cũng đang nổi lên như một hiện tượng và được rất nhiều người sử dụng. Trong bài viết này, tôi sẽ đi vào tìm hiểu và thực hành sử dụng Docker cho dự án Django xem sao.

Tại sao lại dùng Docker

Nói chung tôi là người đơn giản. Tôi thấy Docker được nhiều người dùng quá. Mà nhiều người dùng thì chắc là tốt người ta mới dùng. Mà thứ tốt như vậy thì mình cũng nên dùng cho hợp vào trào lưu, kẻo lại bị lạc hậu so với người ta. Lý do chính đến với Docker của tôi đơn giản thế thôi.

Ngoài ra, lý do phụ là tôi cũng đang có nhu cầu chạy Web trên một môi trường biệt lập với hệ thống. Đương nhiên, nếu không nhu cầu chạy máy ảo hay môi trường biệt lập gì thì bạn cũng chẳng cần nghĩ đến Docker làm gì cho mệt.

Docker đã được quảng cáo rất nhiều. Bạn có thể đọc một số bài giới thiệu về Docker hoặc tham khảo thêm ở trang chủ của Docker. Tuy nhiên, tôi thấy phần lớn các bài viết mới chỉ là lý thuyết chung chung. Mà lý thuyết cần phải gắn liền với thực tiễn. Và bài viết này chính là phần "thực tiễn" đó.

Nói ngắn gọn là chúng ta bắt tay vào thực hành luôn cho nóng. Ở đây, tôi sẽ sử dụng Docker để chạy ứng dụng Web viết bằng Django. Về cơ bản, các framework khác cũng sẽ tương tự như vậy.

Trong bài viết này, tôi giả sử bạn đã có một project Django có sẵn trên máy. Nếu chưa có, bạn có thể xem qua tutorial của Django và tạo ra một project như vậy. Chúng ta sẽ tìm hiểu cách cấu hình Docker trên project có sẵn đó.

Cài đặt

Việc cài đặt Docker lên các hệ điều hành khác nhau, bạn có thể tham khảo ở đây. Đây không phải là nội dung chính của bài viết này nên tôi sẽ không đi vào chi tiết.

Tôi sử dụng Ubuntu 14.04 nên quá trình cài đặt trên máy của tôi có thể tóm tắt như sau:

Update apt chuẩn bị cho quá trình cài đặt.

$ sudo apt-get update
$ sudo apt-get install apt-transport-https ca-certificates
$ sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 \
--recv-keys 58118E89F3A912897C070ADBF76221572C52609D

Sửa file /etc/apt/source.list.d/docker.list với nội dung như sau.

deb https://apt.dockerproject.org/repo ubuntu-trusty main

Update danh sách package và xóa các phiên bản cũ

$ sudo apt-get update
$ sudo apt-get purge lxc-docker

Cài đặt Docker

$ sudo apt-get install docker-engine

Sử dụng docker để phát triển Django

Thông thường, khi phát triển ứng dụng Django nói riêng và các ứng dụng Python nói chung, chúng ta thường sử dụng môi trường ảo của Python (virtualenv hoặc venv). Mục đích của việc này là muốn tạo ra một môi trường biệt lập cho ứng dụng. Tách biệt nó khỏi phần còn lại của hệ thống, nhằm đảm bảo, việc phát triển ứng dụng của chúng ta không ảnh hưởng gì đến hệ thống cả.

Tuy nhiên, các ứng dụng Web sử dụng Django hay bất kỳ framework nào khác, việc sử dụng môi trường ảo cho riêng Python là vẫn chưa đủ. Bởi vì ngoài các package Python cần thiết, còn rất nhiều thành phần khác nữa, ví dụ như cơ sở dữ liệu (PostgreSQL, MySQL, ...), cache (memcached, Redis, ...), v.v... Ngoài ra, nhiều khi chúng ta muốn cấu hình môi trường production để chạy thử trước khi chính thức vận hành ứng dụng, mà chưa có điều kiện hay nhu cầu thuê một server thật, chúng ta cần những giải pháp khác.

Và giải pháp thông thường là sử dụng máy ảo. Chúng ta có thể sử dụng những chương trình như VirtualBox hay VMWare và cài đặt trên máy cá nhân một máy ảo Linux càng giống server càng tốt. Và chúng ta cũng sẽ đăng nhập vào đó, lấy code về cài đặt và cấu hình mọi thứ để trang Web của chúng ta chạy được.

Vấn đề ở đây là, một máy ảo như vậy sẽ chiếm kha khá tài nguyên của hệ thống. Và với một máy tính cá nhân, thì nhiều khi chạy được máy ảo xong thì máy thật cũng không làm gì khác được. Ngoài ra, trong quá trình phát triển, mỗi khi chúng ta muốn cập nhật code mới, thì chúng ta phải làm một công việc khá lòng vòng đó là push code, sau đó login vào máy ảo và pull code về.

Có một giải pháp khác là Vagrant có thể giải quyết vấn đề update code trên máy ảo này. Nó có một cơ chế khá hay là cho phép đồng hộ hệ thống file và thư mục của dự án vào trong máy ảo nên code sẽ được cập nhật gần như ngay lập tức. Tuy nhiên, Vagrant cũng sử dụng dịch vụ bên thứ 3 như VirtualBox để chạy một máy ảo nên vấn đề tiêu tốn tài nguyên hệ thống vẫn chưa được giải quyết.

Gần đây, Docker nổi lên và được cho là có thể giải quyết cả hai vấn đề trên. Nếu thực sự như vậy thì chúng ta có thể yên tâm sử dụng Docker trong công việc của mình rồi. Chúng ta sẽ thử dùng Docker xem nó có thể làm được những gì.

Những gì chúng ta cần khi dùng Docker

Chúng ta có thể tóm tắt lại những yêu cầu khi ảo hóa như sau:

  • Mã nguồn được biên tập ở máy thật và chạy ở máy ảo
  • Việc cập nhật mã nguồn phải gần như ngay lập tức. Tức là mọi thay đổi mã nguồn ở máy thật phải xuất hiện trên máy ảo.
  • Trang Web đang chạy phải có thể xem được từ bên ngoài.

Docker cần phải giải quyết được 3 vấn đề trên trước đã, sau đó chúng ta mới xem xét đến vấn đề tài nguyên hệ thống.

Yêu cầu thứ 3 khá đơn giản, có thể dễ dàng thực hiện bằng port forward. Yêu cầu biên tập và chạy mã nguồn ở hai nơi khác nhau cũng có thể giải quyết được. Chúng ta sẽ mount file và thư mục dự án vào trong container, và chúng ta có thể biên tập ở máy thật và chạy từ container.

Có nhiều cách cấu hình các container khác nhau. Chúng ta sẽ dần dần tìm hiểu từng cách một.

Build tất cả trong một

Giả sử server của bạn sẽ sử dụng hệ điều hành Ubuntu thì chúng ta sẽ sử dụng image Ubuntu để làm cơ sở cấu hình container, sau đó cấu hình thêm các thành phần cần thiết khác.

Trước hết, chúng ta phải pull image ubuntu về máy:

$ sudo docker pull ubuntu

Sau khi pull image này xong, chúng ta có thể tạo một container mới từ image này:

$ sudo docker run -i -t -p 127.0.0.1:8000:8000 ubuntu

Lệnh trên sẽ tạo một container mới với port forward cổng 8000 của máy host (máy thật) vào cổng 8000 của container với những tùy chọn như sau:

  • -i kết nối với stdinstdout
  • -t khởi tạo một terminal
  • -p port forward

Chúng ta cần thay đổi lệnh trên một chút và tạo container với lệnh sau cho phù hợp với mục tiêu của mình:

$ sudo docker run -i -t -p 127.0.0.1:8000:8000 \
-v /home/django_project:/home/code ubuntu

Lệnh trên sẽ tạo ra một container mới với cấu hình như sau:

  • Port forward ở cổng 8000. Như vậy, chúng ta có thể truy cập đến server chạy trong container qua cổng 8000 của máy thật.
  • Thư mục /home/django_project được mount vào trong container ở đường dẫn /home/code. Điều đó có nghĩa là các file trong thư mục /home/django_project có thể truy cập được từ bên trong container ở /home/code.

Hai điều trên chính là những gì chúng ta cần khi dùng Docker để phát triển Django. Bây giờ chúng ta có thể biên tập mã nguồn ở máy thật, chạy nó ở máy ảo và xem kết quả từ trình duyệt của mình. Mã nguồn được thực thi ở một môi trường biệt lập, một container của Docker.

Vagrant cũng có cơ chế mount thư mục tương tự như vậy nhưng Vagrant sử dụng virtual machine nên khá tốn tài nguyên hệ thống, trong khi Docker nhẹ nhàng hơn rất nhiều.

Cấu hình container

Với container đã được tạo ở trên, chúng ta sẽ được đăng nhập vào terminal của nó với dấu nhắc lệnh là #. Từ đây, chúng ta có thể cài đặt các thành phần cần thiết cho dự án của chúng ta như:

  • Python
  • PIP
  • PostgreSQL
  • Django và những thứ cần cho dự án khác

Vậy là chúng ta có thể làm mọi thứ với container này như với một máy tính thông thường. Sau khi hoàn tất, bạn có thể thoát ra khỏi container với lệnh sau:

# exit

Tuy nhiên, ở đây cần phải lưu ý một chút. Nếu bạn không commit container, tất cả những gì bạn vừa làm sẽ không được lưu lại. Do đó, bạn cần phải commit. Trước hết, tìm ID của container với lệnh sau:

$ sudo docker ps -a

Tìm container mà bạn vừa thao tác. Bạn có thể đặt tên cho nó khi commit:

$ sudo docker commit 31d58a70ae41 django_container

Lệnh trên sẽ commit container bạn vừa thao tác và đặt tên cho nó là django_container. Thực ra, sau khi commit, bạn đã tạo ra một image mới cho container vừa rồi. Từ bây giờ, bạn cần khởi động container này để tiếp tục làm việc với dự án Django của mình, bạn có thể dùng lệnh như sau:

$ sudo docker start django_container
$ sudo docker attach django_container

Đơn giản vậy thôi. Nếu không thích container này nữa, bạn có thể xóa nó đi:

$ sudo docker rm django_container

Nếu bạn muốn lưu trữ container mình đã tạo, bạn có thể sử dụng Docker hub. Với Docker hub, bạn có thể thao tác push, pull tương tự như với Github. Ngoài ra, bạn còn có thể đóng gói container này thành một file .tar và chia sẻ nó với những developer khác. Cách làm cụ thể chúng ta sẽ tìm hiểu ở phần tiếp theo.

Đóng gói container

Bạn có thể đóng gói toàn bộ container của mình (bao gồm code dự án, các môi trường Django, PostgreSQL, v.v...) vào một file .tar:

$ sudo docker export django_container > /home/django_container.tar

Sau khi có file này rồi, bạn có thể dễ dàng chia sẻ nó với các developer khác. Khi họ nhận được nó, họ sẽ dễ dàng khởi động đó với lệnh sau:

$ sudo docker load django_container < /home/django_container.tar

Việc gõ các lệnh để build và chạy các container có thể là một công việc nhàm chán. Đó chính là lúc bạn cần đến Dockerfile.

Dockerfile chính là nơi bạn sẽ lưu các cấu hình cho container mà mình cần và có thể dễ dàng chạy container mà không cần phải nhớ những dòng lệnh khô khan và phức tạp. Dockerfile sẽ được sử dụng khá nhiều ở những phần tiếp theo. Nên theo tôi, bạn hãy xem qua nội dung của nó một chút trước khi chúng ta tiếp tục.

Sử dụng Docker Compose cấu hình môi trường dev

Việc xây dựng toàn bộ môi trường chạy Web vào một container có thể chưa phải là điều bạn muốn. Bạn muốn chia môi trường này thành các module khác nhau để có thể tái sử dụng ở các dự án khác. Nhu cầu cơ bản nhất là tách biệt cơ sở dữ liệu và server Web để các dự án khác nhau có thể dùng chung một container cơ sở dữ liệu đó. Docker Compose có thể giúp bạn. Nó sẽ giúp chúng ta có thể cấu hình và chạy nhiều container cùng một lúc cho một dự án (hay bao nhiêu dự án thì tuỳ bạn).

Trước hết, cần phải cài đặt Docker Compose để sử dụng. Có nhiều cách để làm việc này nhưng dễ dàng nhất theo tôi là sử dụng pip:

$ pip install docker-compose

Thông tin về Docker Compose, bạn có thể tham khảo thêm ở đây. Nói chung là tôi không muốn giới thiệu nhiều về nó, áp dụng nó vào thực tiễn sẽ là dễ hiểu nhất.

Nói qua về ý tưởng trong phần này, chúng ta sẽ tạo ra hai container riêng biệt: một container để chạy Web và một container chứa cơ sở dữ liệu.

Với dự án đang có, bạn tạo một Dockerfile mới để cấu hình cho container Web với nội dung như sau:

FROM python:latest
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt

Nếu bạn chưa quen với Dockerfile, bạn có thể tham khảo thêm tài liệu này.

Dockerfile trên nhằm cấu hình một image mới từ image Python phiên bản mới nhất (3.5.1), sau đó thêm thư mục /code và cài đặt các package Python cần thiết đã được định nghĩa ở file requirements.txt. Container cho Web server sẽ được tạo ra từ image này.

Dockerfile trên để cấu hình container Web, container cho cơ sở dữ liệu chúng ta không phải cấu hình image cho nó, chỉ cần sử dụng image postgres chuẩn là đủ rồi.

Tiếp theo, tạo file docker-compose.yml với nội dung:

version: '2'
services:
  db:
    image: postgres
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db

File docker-compose.yml sẽ định nghĩa các container mà chúng ta cần. Hiện tại, chúng ta cần một container Web và một container cho cơ sở dữ liệu. Ngoài ra, file này còn một số thông tin cấu hình khác cho các container để chúng hoạt động theo đúng ý đồ của chúng ta. Bạn có thể tham khảo thêm ở đây để biết thêm về cấu trúc của file này.

Cấu hình trên khá đơn giản, chúng ta sẽ tạo ra 2 container. Container postgres là container cơ sở dữ liệu. Chúng ta sẽ sử dụng hoàn toàn cấu hình mặc định từ image postgres chuẩn là đủ. Container Web, sau khi cấu hình và cài đặt theo image được định nghĩa ở Dockerfile, chúng ta thêm một số cấu hình khác như port forward, mount thư mục và chạy server dev cho Django.

Chúng ta chạy server cơ sở dữ liệu ở một container độc lập, nên chúng ta cần thay đổi settings để Django có thể kết nối đến nó. Settings mới sẽ tương tự như dưới đây:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'postgres',
        'USER': 'postgres',
        'HOST': 'db',
        'PORT': 5432,
    }
}

Vậy là quá trình cầu hình đã xong, việc cần làm còn lại chỉ đơn giản là tạo container từ cấu hình vừa rồi và khởi động chúng:

$ docker-compose up

Lần đầu tiên chạy lệnh này sẽ mất một chút thời gian do hệ thống phải pull các image về sau đó cài đặt và cấu hình các package khác. Những lần tiếp theo, thời gian khởi động sẽ nhanh hơn do chỉ cần đồng bộ các file và thư mục là đủ.

Cuối cùng, nếu mọi chuyện bình thường, chúng ta có thể truy cập http://localhost:8000, kết quả sẽ như sau:

django-http.png

Vậy là xong. Chúng ta build thành công môi trường dev cho dự án Django của chúng ta. Thậm chí container cơ sở dữ liệu còn có thể sử dụng ở nhiều dự án khác nhau nữa.

Sử dụng Docker Compose cấu hình môi trường production

Có thể sẽ có lúc bạn muốn cấu hình thử môi trường production trên chính máy tính của mình. Phần này sẽ trình bày cách làm với Docker.

Với việc sử dụng Docker Compose, việc cấu hình các container đã khá dễ dàng. Vì vậy, chúng ta sẽ tiếp tục sử dụng Docker và Docker Compose cấu hình thử môi trường production xem sao.

Trước hết, bạn cần phải biết việc deploy Django đã. Nếu chưa biết, bạn có thể tham khảo thêm ở hướng dẫn này.

Nói chung, khi deploy Django, stack của chúng ta sẽ bao gồm các thành phần sau:

  • NGINX là server gateway đồng thời cũng là một reverse proxy.
  • WSGI server (uWSGI hoặc gunicorn) chạy Django.
  • Server cơ sở dữ liệu (PostgreSQL, MySQL, ...).
  • Server khác nếu cần (memcached, Redis, ...)

Chúng ta sẽ xây dựng các container của Docker tương tự như stack này. Stack của chúng ta được minh họa như hình dưới đây:

django-stack.png

Chúng ta có thể sử dụng uWSGI hoặc gunicorn để làm server WSGI chạy Django. uWSGI cho hiệu suất tốt hơn nhưng gunicorn dễ sử dụng hơn. Trong bài viết này, tôi quyết định sử dụng uWSGi, vừa là vì hiệu suất của nó, vừa là vì nó khó sử dụng nên chúng ta sẽ học được nhiều hơn.

Bắt đầu cấu hình các container thôi. File docker-compose.yml sẽ có nội dung như dưới đây:

version: '2'
services:
  db:
    restart: always
    image: postgres
    volumes_from:
      - data
    depends_on:
      - data
  data:
    restart: always
    image: postgres
    volumes:
      - /var/lib/postgresql
    command: "true"
  nginx:
    restart: always
    build: ./nginx/
    ports:
      - "80:80"
    volumes:
      - /www/static/
    volumes_from:
      - web
    links:
      - web:web
    depends_on:
      - web
  web:
    restart: always
    build: .
    expose:
      - "8000"
    env_file:
      - .env
    command: uwsgi --http :8000 --module mysite.wsgi
    volumes:
      - .:/code
    depends_on:
      - db

Ở đây, chúng ta sẽ xây dựng các container như sau:

Container web sẽ làm nhiệm vụ của server WSGI. Về cơ bản, container này khá giống cấu hình container Web của phần trước. Một số điểm khác biệt đó là nó không forward cổng 8000 mà chỉ "expose" cho container nginx mà thôi. Ngoài ra, nó sẽ sử dụng uWSGI để khởi động server cho Django.

Container nginx sẽ làm nhiệm vụ của một reverse proxy, tiếp nhận các truy vấn từ người dùng và chuyển sang cho server WSGI. Ngoài ra, các file tĩnh (CSS, JavaScript, v.v..) sẽ không được Django phục vụ nữa, nhiệm vụ này cũng được chuyển cho NGINX.

Ngoài ra chúng ta có một container cho PostgreSQL và một container để chứa dữ liệu. Việc tách dữ liệu ra lưu trữ ở một container riêng là để tránh sự phụ thuộc của dữ liệu và container db, đảm bảo rằng, dữ liệu sẽ vẫn còn nếu chúng ta thay db bằng một container khác.

Ở đây, các container cho cơ sở dữ liệu rất đơn giản, chúng ta gần như sử dụng toàn bộ cấu hình từ image tiêu chuẩn. Container web khá giống phần trước nên tôi nghĩ không cần giải thích gì nhiều. Container nginx là phần mới nên tôi sẽ nói kỹ hơn về nó.

Cấu hình container nginx

Để cấu hình container nginx, chúng ta sẽ tạo một thư mục riêng cho nó. Bởi vì image chuẩn là chưa đủ, chúng ta cần cấu hình một image mới. Do đó, việc cấu hình này sẽ cần đến Dockerfile và file cấu hình cho server nên tổ chức chúng vào thư mục riêng sẽ quản lý dễ hơn.

Dockerfile cho nginx sẽ có nội dung như sau:

FROM nginx
ADD django_site.conf /etc/nginx/conf.d/

Cấu hình trên khá đơn giản. Chúng ta sẽ tạo một image mới từ image nginx chuẩn, và thêm cầu hình cho trang Web của dự án.

Dưới đây là cấu hình cho server NGINX trở thành reverse proxy cho WSGI, đồng thời nó có nhiệm vụ phục vụ các file tĩnh (CSS, JavaScript, v.v...) ở đường dẫn /static.

server {
    listen        80;
    server_name   vpyeu.local;
    charset       utf-8;

    location /static {
        alias /code/static;
    }

    location / {
        proxy_pass        http://web:8000;
        proxy_set_header  Host              $host;
        proxy_set_header  X-Real-IP         $remote_addr;
        proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;
    }
}

Cấu hình này không có gì khó hiểu cả, nếu bạn đã từng làm việc với NGINX rồi. Ở đây tôi chọn server_namevpyeu.local để tránh xung đột với config có sẵn của NGINX. Bạn có thể ghi đè lên config mặc định của NGINX (/etc/nginx/nginx.conf) cũng được. Chỉ có một lưu ý nhỏ là bạn cần sửa file /etc/hosts để có thể truy cập đến localhost thông qua server_name.

Vậy là toàn bộ công việc cần làm đã xong. Docker Compose chỉ cần thêm một chút cầu hình cho container này từ image vừa được tạo ra như mount thư mục static, port forward, v.v nữa là xong. Bây giờ, chúng ta chỉ cần build và khởi động các container là xong. Chúng ta sẽ xem kết quả ở cổng 80 (http://vpyeu.local/) chứ không phải ở cổng 8080 nữa.

Chạy bash bên trong container

Làm theo tất cả những hướng dẫn ở trên, bạn có thấy thiếu gì không?

Thực ra, đến đây, bạn chỉ có thể chạy Web Django khi chưa có app nào thôi. Bạn hoàn toàn chưa migrate database, chưa tạo user, và có thể sẽ còn vài thao tác cần khởi tạo nữa trước khi ứng dụng của chúng ta có thể hoạt động.

Ví dụ, bạn muốn migrate database và tạo một superuser thì phải làm thế nào? Có một cách để làm việc này, đó là chạy lệnh sau để migrate:

$ docker-compose run web python manage.py migrate

Và dùng lệnh tương tự để tạo một admin:

$ docker-compose run web python manage.py createsuperuser

Tuy nhiên, cần lưu ý là mỗi lệnh như vậy sẽ tạo ra một container mới. Và may mắn cho chúng ta là nó sẽ cấu hình giống hệt container web, và do đó nó cũng kết nối đến cơ sở dữ liệu ở container db. Vì vậy, các lệnh migrate hay createsuperuser ở trên sẽ ghi vào cơ sở liệu này. Thế nên, chúng ta có thể sử dụng trang Web như bình thường.

Nhưng một tác dụng phụ là chúng ta đã tạo ra 2 container thừa thãi chẳng để làm gì. Tất nhiên là bây giờ bạn có thể xóa chúng đi, rất đơn giản. Nhưng tại sao phải làm thế khi chúng ta có cách khác hay hơn?

Thông thường, nếu là server thật, chúng ta phải ssh vào server đó và chạy các lệnh. Container của Docker cũng có cơ chế cho phép chúng ta làm điều tương tự.

Chúng ta có thể chạy bash từ container với lệnh sau, sử dụng ID của container:

$ sudo docker exec -i -t 34f0d3e373f7 bash

Hoặc sử dụng tên của container

$ sudo docker exec -i -t mysite_web bash

Vậy là chúng ta đang "ở trong" bash của container là có thể thao tác tùy ý với container này. Chúng ta có thể dễ dàng migrate, tạo admin mà không cần tạo container nào khác.

Update

Security

Vì những lý do về bảo mật, chúng ta chỉ nên port forward cho container nginx mà thôi. Các container khác chúng ta chỉ "expose" và liên kết các container với nhau thông qua links.

Container db và cả container data để build từ image postgres chuẩn. Nếu chúng ta không chỉ định username và password thì bất cứ ai cũng có thể truy cập đến chúng. Ngoài ra, các dữ liệu như SECRET_KEY, hay PASSWORD của PostgreSQL chúng ta cần chuyển sang sử dụng biến môi trường thay vì hard code vào settings.

Docker cho phép chúng ta dễ dàng thiết lập các biến môi trường thông qua env_file. Ví dụ, với container WSGI, chúng ta có thể cấu hình các biến môi trường như sau:

PYTHONBUFFERED=1
SECRET_KEY=its-a-secret-to-everybody
DB_NAME=your-database-name
DB_USER=your-username
DB_PASSWORD=your-password
DB_HOST=db
DB_PORT=5432

Lưu nội dung trên vào một file, ví dụ .env, chúng ta có thể dễ dàng cấu hình container các biến môi trường này:

env_file:
  - .env

Bây giờ, chúng ta cần thay đổi settings để lấy dữ liệu từ các biến môi trường này:

SECRET_KEY = os.environ.get('SECRET_KEY', '')
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ.get('DB_NAME', ''),
        'USER': os.environ.get('DB_USER', ''),
        'PASSWORD': os.environ.get('DB_PASSWORD', ''),
        'HOST': os.environ.get('DB_HOST', ''),
        'PORT': os.environ.get('DB_PORT', ''),
    }
}

Chúng ta có thể làm điều tương tự với hai container dbdata.

Ngoài ra, chúng ta có thể thiết lập sử dụng HTTPS thay cho HTTP ngay trên chính Docker. Ví dụ, chúng ta có thể thay đổi config của NGINX như sau:

server {
    listen        80;
    server_name   vpyeu.local;
    rewrite       ^/(.*) https://$host/$1 permanent;
}

server {
    listen        443 ssl;
    server_name   vpyeu.local;
    charset       utf-8;
    access_log    /www/log/access.log combined;
    error_log     /www/log/error.log error;

    ssl_certificate       /www/ssl/ssl.crt;
    ssl_certificate_key   /www/ssl/ssl.key;

    location /static {
        alias /www/static;
    }

    location /media {
        alias /www/animals;
    }

    location / {
        proxy_pass        http://web:8000;
        proxy_redirect    off;
        proxy_set_header  Host              $http_host;
        proxy_set_header  X-Real-IP         $remote_addr;
        proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;
    }
}

Config trên sẽ chuyển toàn bộ truy vấn sử dụng HTTP thành HTTPS. Từ bây giờ, chúng ta có thể thiếp lập thuộc tính secure cho cookie được rồi:

CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True

Cấu hình trên yêu cầu bạn phải có một SSL certificate để sử dụng HTTPS. Bạn có thể mua lấy một giấy phép như vậy. Tuy nhiên, ở localhost thì chúng ta chưa cần làm như vậy. Chúng ta có thể tạo ra một giấy phép SSL với openssl như sau:

$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout mysite/nginx/ssl.key -out mysite/nginx/ssl.crt

Chỉ có một vấn đề nhỏ, đó là khi tự tạo ra SSL certificate chúng ta sẽ gặp phải vấn đề sau:

untrusted-connection.png

Tất nhiên là bạn có thể bỏ qua vấn đề này để truy cập trang bình thường. Và kết quả sẽ như sau:

django-https.png

Sử dụng uwsgi_params

Sau khi tham khảo một số nơi, và đặc biệt là tài liệu của uWSGI, thì uWSGI có hỗ trợ NGINX. Nên tôi nghĩ, chúng ta nên sử dụng uwsgi_params cho NGINX thì tốt hơn.

Config của NGINX sẽ như sau:

upstream django {
    server web:8000;
}

server {
    listen        80;
    server_name   vpyeu.local;
    rewrite       ^/(.*) https://$host/$1 permanent;
}

server {
    listen        443 ssl;
    server_name   vpyeu.local;
    charset       utf-8;
    access_log    /www/log/access.log combined;
    error_log     /www/log/error.log error;

    ssl_certificate       /www/ssl/ssl.crt;
    ssl_certificate_key   /www/ssl/ssl.key;

    location /static {
        alias /www/static;
    }

    location /media {
        alias /www/animals;
    }

    location / {
        include       uwsgi_params;
        uwsgi_pass    django;
        uwsgi_param   Host                $http_host;
        uwsgi_param   X-Real-IP           $remote_addr;
        uwsgi_param   X-Forwarded-For     $proxy_add_x_forwarded_for;
        uwsgi_param   X-Forwarded-Proto   $http_x_forwarded_proto;
    }
}

Sử dụng uwsgi_paramsuwsgi_pass có thể khiến bạn gặp lỗi 502 Bad Gateway khi kết nối từ container nginx sang container web. Khi đó, bạn cần thêm một chút cấu hình, nhất là thay đổi user chạy uWSGI. Vì cần cấu hình khá nhiều nên chúng ta sẽ viết chúng vào một file, uwsgi.ini với nội dung như sau:

[uwsgi]
# thư mục chứa mã nguồn Django
chdir = /code
# File wsgi của Django
module = mysite.wsgi
# Socket, để kết nối từ NGINX
socket = 0.0.0.0:8000
# master
master = true
# số worker tối đa
processes = 10
# thay đổi user để tránh lỗi 502
chown-socket = www-data:www-data

Và thay đổi lệnh ở container web thành:

uwsgi --ini uwsgi.ini

Kết luận

Docker thực sự cung cấp cho chúng ta một giải pháp mới cho công việc ảo hoá. Thay vì cách làm truyền thống là tạo phần cứng ảo và cài đặt nguyên một hệ điều hành lên đó, Docker đóng gói các container và chạy chúng trên chính máy thật. Nó thật tới mức, nếu Docker của bạn có chạy NGINX, ở ngay trên máy thật, chúng ta cũng thấy có tiến trình của NGINX đang chạy, giống hệt các tiến trình thông thường khác.

docker-process.png

Docker container chạy trên máy thật và chia sẻ tài nguyên với chính máy thật, nên hoạt động nhẹ nhàng hơn hẳn các phương pháp ảo hoá khác. Ngoài ra, việc khởi động và tắt một container cũng rất nhanh chóng. Như trường hợp trên của chúng ta, chúng ta đang chạy cùng một lúc tới 4 container mà tài nguyên hệ thống cũng chưa tốn mấy.

docker-resource.png

Thử tưởng tượng điều gì xảy ra nếu chúng ta chạy 4 máy ảo trên VirtualBox cùng một lúc. Tôi tin rằng , ngay cả khi chỉ cung cấp cho máy ảo cấu hình tối thiểu, máy tính của chúng ta sẽ tốn một lượng tài nguyên không nhỏ, và có lẽ, phần còn lại chẳng còn đủ để chúng ta code gì nữa.

Docker là một giải pháp mà chúng ta có thể dùng khi cần đến ảo hoá trong các project nói chung, không nhất định là project Django.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí