Hướng dẫn lập trình Flask – Phần 19: Triển khai ứng dụng với Docker

flask_tutorial_1

Trong phần này, chúng ta sẽ tìm hiểu cách triển khai ứng dụng Myblog với công nghệ container và nền tảng Docker.

Để giúp cho bạn dễ theo dõi, sau đây là danh sách các bài viết trong loạt bài hướng dẫn này:

Bạn có thể truy cập mã nguồn cho phần này tại GitHub.

Trong Phần 17, chúng ta đã tìm hiểu cách triển khai ứng dụng với dịch vụ máy chủ truyền thống. Quá trình này đòi hỏi chúng ta phải cài đặt và thiết lập mọi thứ trên máy chủ. Tiếp theo, trong Phần 18, chúng ta tìm hiểu một hướng tiếp cận hoàn toàn khác khi triển khai ứng dụng trên nền tảng Heroku. Đây là một loại dịch vụ kiểm soát hoàn toàn các tác vụ cấu hình và triển khai ứng dụng, nhờ đó bạn không cần phải thực hiện các tác vụ này và có thể thể tập trung vào việc phát triển. Hôm nay, chúng ta sẽ tìm hiểu phương án thứ ba để triển khai ứng dụng dựa trên các container (đóng gói), cụ thể hơn là với công nghệ container do Docker cung cấp. Độ phức tạp và số lượng các tác vụ của phương án này nằm ở khoảng giữa phương án thứ nhất và phương án thứ hai,

Các container được tạo ra dựa trên công nghệ ảo hóa bán phần (lightweight virtualization). Công nghệ này cho phép ứng dụng và các thành phần phụ thuộc của nó có thể được thi hành trong một môi trường riêng biệt nhưng không cần để giải pháp ảo hóa toàn phần như là máy ảo (virtual machine) vốn cần sử dụng nhiều tài nguyên hơn và có thể ảnh hưởng nhiều đến tốc độ thực thi của ứng dụng. Một hệ thống được cấu hình để chứa các container (container host) có thể thực hiện đồng thời nhiều ứng dụng trong các container khác nhau. Các container này sẽ sử dụng chung phần nhân (kernel) của hệ thống và có thể truy xuất trực tiếp đến phần cứng của hệ thống. Điều này tương phản với các máy ảo khi chúng phải mô phỏng hoạt động của toàn bộ hệ thống, bao gồm bộ vi xử lý, đĩa cứng, các thiết bị phần cứng, nhân …

Mặc dù cùng sử dụng chung phần nhân của hệ thống, các container mang tính độc lập rất cao. Mỗi container có hệ thống file riêng và có thể dựa trên một hệ điều hành khác với máy chủ chứa nó (container host). Ví dụ như bạn có thể có các container dựa trên hệ điều hành Ubuntu trên máy chủ đang chạy hệ điều hành Fedora và ngược lại. Và dù công nghệ về container chạy với mã gốc (native) trong các hệ thống Linux, chúng ta cũng có thể chạy các container Linux trên Windows hoặc Mac OS X. Điều này cho phép bạn kiểm tra quá trình triển khai ứng dụng trên hệ thống phát triển của bạn và kết hợp công nghệ container vào quy trình phát triển nếu bạn muốn.

Trong bài viết này, chúng ta sẽ không đi quá sâu vào các khái niệm trong Docker và containter. Nếu bạn muốn tìm hiểu thêm về công nghệ này, bạn có thể tham khảo một số bài viết về container và Docker tại trang tài liệu của Docker, Docker cho người mới bắt đầu

Cài đặt Docker CE

Dù Docker không phải là nền tảng duy nhất cho công nghệ container, nó là nền tảng phổ biến nhất hiện nay. Vì vậy, chúng ta sẽ sử dụng Docker để minh họa cho việc triển khai ứng dụng với công nghệ này. Hiện nay có hai phiên bản của Docker, một bản miễn phí cho cộng đồng (Community Edition – CE) và một bản tính phí dành cho các doanh nghiệp (Enterprise Edition – EE). Chúng ta sẽ sử dụng bản CE vì nó hoàn toàn có thể đáp ứng nhu cầu thử nghiệm ứng dụng của chúng ta.

Để bắt đầu, bạn cần cài đặt Docker CE trên hệ thống của bạn. Bạn có thể tải về và sử dụng chương trình cài đặt dành cho các hệ điều hành Windows, Mac OS và một số bản phân phối Linux khác nhau từ trang chủ của Docker. Nếu bạn đang sử dụng hệ điều hành Microsoft Windows, lưu ý là Docker CE cần có Hyper-V để có thể hoạt động. Chương trình cài đặt có thể tự động bật chức năng này, tuy nhiên Hyper-V sẽ làm cho các phần mềm ảo hóa khác như là VirtualBox ngừng hoạt động.

Sau khi đã cài đặt Docker CE, bạn cần kiểm tra tiến trình cài đặt có thành công hay không bằng cách nhập lệnh sau vào trong một cửa sổ lệnh:

Tạo image cho container

Bước đầu tiên để xây dựng một container cho ứng dụng là tạo một image cho nó. Một image là một khuôn (template) để tạo ra một container. Nó chứa các thành phần đại diện cho hệ thống file và các thiết lập khác như là giao tiếp mạng, các tùy chọn khởi động … cho container.

Cách đơn giản nhất để tạo một image cho ứng dụng của bạn là bắt đầu một container với hệ điều hành mà bạn muốn sử dụng (Ubuntu, Fedora …), kết nối và đăng nhập vào hệ thống lệnh (shell) của container này và cài đặt ứng dụng bằng phương pháp thủ công (có thể sử dụng phương pháp tương tự trong Phần 17 để cài đặt ứng dụng trên máy chủ truyền thống). Sau khi cài đặt ứng dụng, bạn có thể lấy ra một ảnh chụp (snapshot) của container và đó sẽ là một image. Bạn hoàn toàn có thể làm theo quy trình này với các lệnh chính thức của Docker. Tuy nhiên, chúng ta sẽ không thảo luận về cách làm này vì rất bất tiện nếu bạn phải tiến hành cài đặt thủ công ứng dụng mỗi khi bạn cần tạo ra một image mới.

Một phương pháp hay hơn là viết một kịch bản (script) và dùng lệnh docker build để tạo ra image từ kịch bản này. Lệnh này sẽ đọc và thực hiện các chỉ thị từ một file gọi là Dockerfile để tạo ra image cần thiết. Về cơ bản, file Dockerfile là một loại kịch bản để cài đặt ứng dụng với các thiết lập cho container, và nhiệm vụ của chúng ta là tạo ra file này.

Dưới đây là một Dockerfile cơ bản cho Myblog:

Dockerfile: Dockerfile cho Myblog.

Mỗi dòng trong Dockerfile là một lệnh. Lệnh FROM định nghĩa image nguồn mà chúng ta sẽ sử dụng để tạo ra image cho ứng dụng. Ý tưởng chính ở đây là sử dụng một image có sẵn và thay đổi cho phù hợp với ứng dụng của chúng ta. Các image được tham chiếu bởi tên và thẻ (tag) và được ngăn cách bằng dấu hai chấm. Ở đây, thẻ có tác dụng như là phiên bản và cho phép một image có thể có nhiều biến thể khác nhau. Trong ví dụ ở trên, chúng ta chọn image python. Đây là image chính thức cho Python do Docker cung cấp. Thẻ cho image này cho phép bạn chỉ định phiên bản của trình thông dịch Python và hệ điều hành đi kèm. Thẻ 3.8-alpine sẽ cho phép chúng ta sử dụng trình thông dịch Python phiên bản 3.8 với hệ điều hành Alpine Linux. Bản phân phối Alpine của Linux thường được sử dụng cho Docker hơn là các bản phân phối phổ biến khác như là Ubuntu bởi vì nó có kích thước nhỏ. Bạn có thể tìm các tag có thể được sử dụng cho image Python từ repository của các image cho Python.

Lệnh RUN thi hành một lệnh bất kỳ trong ngữ cảnh của container. Điều này cũng tương tự như khi bạn nhập lệnh trong cửa sổ lệnh. Tham số của lệnh này là adduser –D myblog thực chất là một lệnh của Linux để tạo ra một user mới có tên là myblog. Hầu hết các image đều có user mặc định là root, nhưng chúng ta không nên dùng user này để thi hành ứng dụng, vì vậy, chúng ta cần tạo user riêng cho ứng dụng như trên.

Lệnh WORKDIR sẽ thiết lập thư mục mặc định để cài đặt ứng dụng. Khi chúng ta tạo ra user myblog ở trên, một thư mục gốc cho user này cũng sẽ được hệ thống tự động tạo ra, và chúng ta sẽ sử dụng thư mục đó để làm thư mục mặc định. Thư mục này cũng sẽ là thư mục đích cho các lệnh còn lại trong Dockerfile.

Lệnh COPY sẽ sao chép các file trong máy tính của bạn vào hệ thống file của container. Lệnh này cần ít nhất hai tham số: đường dẫn đến file nguồn và file đích. Đường dẫn đến file nguồn phải là vị trí tương đối so với thư mục có chứa Dockerfile. Đường dẫn đến file đích có thể là đường dẫn tuyệt đối hoặc đường dẫn tương đối so với thư mục được chỉ định trong lệnh WORKDIR trước đó. Với lệnh COPY đầu tiên này, chúng ta muốn sao chép file requirements.txt vào thư mục gốc của user myblog trong hệ thống file của container.

Sau khi đã có file requirements.txt trong container, chúng ta có thể khởi tạo môi trường ảo với lệnh RUN. Đầu tiên chúng ta tạo môi trường ảo với lệnh RUN thứ nhất và sau đó cài đặt tất cả các thành phần cần thiết cho ứng dụng từ file requirements.txt với lệnh RUN thứ hai. Tuy nhiên, vì file này không có gunicorn để làm Web server, chúng ta phải cài đặt nó với lệnh RUN thứ ba. Nếu không thích cách làm này, bạn cũng có thể thêm gunicorn trực tiếp vào file requirements.txt.

Tiếp theo , chúng ta sẽ cài đặt ứng dụng vào container bằng cách sao chép các thư mục app chứa mã nguồn chính của ứng dụng, thư mục migrations với các mã kịch bản để cập nhật cơ sở dữ liệu, và các file myblog.pyconfig.py từ thư mục gốc của ứng dụng trong ba lệnh COPY theo sau. Trong tiến trình này, chúng ta cũng sao chép một file mới gọi là boot.sh. Nội dung của file này sẽ được đề cập ở phần sau.

Lệnh RUN chmod bảo đảm rằng file boot.sh sẽ được thiết lập như là một file thực thi (executable). Nếu bạn đang sử dụng một hệ thống file Unix như là trong Linux hoặc Mac OS X và file nguồn của bạn là một file thực thi, file đích cũng sẽ được tự động thiết lập là một file thực thi. Tuy nhiên, chúng ta vẫn cần có dòng lệnh này trong trường hợp bạn đang sử dụng Windows.

Lệnh ENV sẽ khai báo một biến môi trường trong container. Chúng ta sử dụng lệnh này để khai báo biến môi trường FLASK_APP vì lệnh flask để khởi động ứng dụng sẽ cần dùng đến nó.

Lệnh RUN chown sau đó dùng để thiết lập chủ quyền cho các thư mục và file mà chúng ta đã sao chép vào thư mục /home/myblog trong container. Mặc dù chúng ta đã tạo ra tài khoản myblog trong phần đầu của Dockerfile, tài khoản mặc định để thi hành các lệnh trong Dockerfile vẫn là root. Vì vậy, chúng ta cần đổi chủ quyền của các file và thư mục này sang tài khoản myblog để tài khoản này có thể làm việc được với ứng dụng khi container bắt đầu hoạt động.

Lệnh USER trong dòng kế tiếp sẽ thiết lập myblog là user mặc định cho các lệnh tiếp theo và khi container bắt đầu hoạt động.

Lệnh EXPOSE thiết lập cấu hình cho các cổng (port) mà container sẽ dùng cho ứng dụng. Việc này là cần thiết để Docker có thể thiết lập đúng cấu hình mạng trong container. Ở đây, chúng ta sử dụng cổng tiêu chuẩn của Flask là 5000, nhưng bạn có thể chọn một cổng bất kỳ.

Và cuối cùng, lệnh ENTRYPOINT định nghĩa lệnh mặc định sẽ được thi hành khi container khởi động. Đây là lệnh để khởi động chương trình Web server của ứng dụng. Để bảo đảm tính chặt chẽ của cấu trúc chương trình, chúng ta sẽ tạo ra một mã kịch bản riêng cho công việc này. Đây cũng là file boot.sh mà chúng ta đã sao chép vào container trước đó. Và sau đây là nội dung của file này:

boot.sh: Kịch bản khởi động của container.

Đây là một kịch bản khởi động tiêu chuẩn và rất giống với nhứng gì mà chúng ta đã làm khi triển khai ứng dụng trong Phần 17Phần 18. Trong kịch bản này, chúng ta kích hoạt môi trường ảo, tiến hành cập nhật cơ sở dữ liệu nhờ các mã chuyển đổi dữ liệu, tạo ra các file phiên dịch cho các ngôn ngữ được hỗ trợ, và cuối cùng là khởi động web server gunicorn.

Bạn cần lưu ý đến lệnh exec trước lệnh gunicorn. Với kịch bản cho chế độ dòng lệnh, lệnh này sẽ thay thế tiến trình (process) đang thực hiện kịch bản này bằng lệnh sắp được thi hành, thay vì tạo ra một tiến trình mới. Điều này rất quan trọng bởi vì Docker sẽ kết hợp vòng đời của container với tiến trình đầu tiên được thực thi bên trong nó. Trong những trường hợp như trên, khi tiến trình khởi động ứng dụng không phải là tiến trình chính của container, chúng ta cần bảo đảm rằng tiến trình chính sẽ thay thế tiến trình đầu tiên để container không bị Docker chấm dứt hoạt động.

Một trong những điểm thú vị của Docker là bất kỳ thông tin gì được container ghi vào stdout hoặc stderr sẽ được lưu vào nhật ký của container. Vì vậy, chúng ta sử dụng cả hai tham số --access-logfile--error-logfile với giá trị - để gởi nhật ký của web server vào kết xuất chuẩn (standard output) để chúng có thể được lưu vào nhật ký container của Docker.

Sử dụng Dockerfile này, chúng ta có thể tạo image cho ứng dụng với lệnh sau:

Tham số -t trong lệnh docker build ở trên sẽ thiết lập tên(name) và thẻ (tag) cho container mới được tạo ra. Dấu . chỉ định thư mục gốc để tạo container. Đây cũng là thư mục có chứa Dockerfile. Quá trình tạo image sẽ lần lượt thực hiện các lệnh trong Dockerfile, tạo ra image cần thiết trên hệ thống của bạn.

Bạn có thể xem danh sách các image có trong hệ thống của bạn với lệnh docker images:

Danh sách này bao gồm cả image mới tạo và image gốc được dùng để tạo ra nó. Mỗi khi bạn cập nhật ứng dụng, bạn có thể cập nhật image bằng cách dùng lệnh build như trên.

Khởi động Container

Sau khi đã có image, bạn có thể chạy phiên bản sử dụng container của ứng dụng. Để làm việc này, bạn cần sử dụng lệnh docker run với một vài tham số khác nhau. Chúng ta sẽ bắt đầu với một ví dụ cơ bản:

Tùy chọn --name khai báo tên của container mới. Tùy chọn –d cho Docker biết phải chạy container trong chế độ nền (background). Nếu không có tùy chọn này, container sẽ được thực thi trong chế độ nổi (foreground) và không cho bạn tiếp tục nhập lệnh vào cửa sổ lệnh. Tùy chọn –p thiết lập ánh xạ giữa các cổng do container sử dụng và các cổng của máy chứa container (host). Ví dụ trên sẽ ánh xạ cổng 5000 của container vào cổng 8000 của host. Nhờ đó, bạn có thể truy cập ứng dụng qua cổng 8000 mặc dù ứng dụng sử dụng cổng 5000 bên trong container. Tùy chọn --rm sẽ xóa bỏ container khi nó ngừng hoạt động. Dù việc này không bắt buộc, nhưng thường thì các container đã chấm dứt hoạt động không còn cần thiết nữa, vì vậy chúng có thể được tự động xóa bỏ. Tham số cuối cùng trong lệnh trên là tên và thẻ của image được dùng cho container. Sau khi thi hành lệnh trên, bạn có thể truy cập ứng dụng tại địa chỉ http://localhost:8000.

Khi lệnh docker run được thực thi, Docker sẽ in ra định danh (ID) được gán cho container mới dưới dạng một chuỗi các ký tự thập lục phân. Bạn có thể dùng giá trị này khi cần tham chiếu đến container trong các lệnh sau này. Nhưng thường thì chúng ta chỉ cần một số ký tự đầu chứ không cần nguyên chuỗi khi tham chiếu – chỉ vừa đủ để đảm bảo giá trị này là duy nhất.

Nếu cần biết có container nào đang chạy trong hệ thống, bạn có thể sử dụng lệnh docker ps:

Bạn có thể thấy rằng ngay cả lệnh docker ps cũng chỉ sử dụng giá trị rút gọn của các chuỗi ID cho container. Nếu muốn ngừng hoạt động của một container, bạn có thể sử dụng lệnh docker stop:

Nếu bạn còn nhớ, có một số các tùy chọn trong cấu hình của ứng dụng có sử dụng các biến môi trường. Ví dụ như khóa bí mật của Flask, địa chỉ truy cập cơ sở dữ liệu và các tùy chọn cho máy chủ email sử dụng các giá trị từ các biến môi trường. Trong ví dụ về lệnh docker run ở trên, chúng ta bỏ qua các tùy chọn này, do đó các thông số này sẽ có giá trị mặc định trong ứng dụng.

Trong thực tế, bạn sẽ cần phải thiết lập các biến môi trường này bên trong container. Trong phần trên, bạn đã thấy lệnh ENV trong Dockerfile để thiết lập giá trị cho biến môi trường, và đây là một lệnh rất hữu ích cho các biến môi trường tĩnh. Nhưng nó lại rất bất tiện khi cần sử dụng cho các biến môi trường động và phục thuộc vào tiến trình cài đặt bởi vì nếu bạn đưa các biến này vào image, người sử dụng sẽ phải tạo lại image này với các giá trị riêng cho các biến môi trường của họ. Điều chúng ta muốn là người sử dụng sẽ có thể dùng lại các image mà chúng ta đã xây dựng mà không phải thay đổi gì cả. Để đáp ứng nhu cầu này, chúng ta sẽ sử dụng tùy chọn –e khi thực hiện lệnh docker run. Tùy chọn này cho phép chúng ta thiết lập các biến môi trường trong thời gian thực hiện (run-time) như ví dụ sau đây:

Trong thực tế thì các lệnh docker run dài với nhiều biến môi trường như trên không phải làm trường hợp hiếm thấy.

Sử dụng các dịch vụ đóng gói sẵn của các nhà cung cấp thứ ba

Phiên bản dùng container của ứng dụng đã được xây dựng thành công, nhưng chúng ta vẫn chưa đụng chạm đến phần lưu trữ. Cụ thể là cho đến thời điểm này, chúng ta vẫn chưa thiết lập biến môi trường DATABASE_URL, vì vậy ứng dụng sẽ sử dụng cơ sở dữ liệu theo thiết lập mặc định là SQLite. Và bởi vì SQLite sẽ ghi và đọc dữ liệu từ file, theo bạn thì điều gì sẽ xảy ra nếu chúng ta ngừng và xóa bỏ container? File chứa dữ liệu của chúng ta sẽ biến mất.

Cũng tương tự như với Heroku, hệ thống file trong container là hệ thống file ngắn hạn (ephemeral), có nghĩa là bất kỳ file nào không có trong container khi container được khởi tạo sẽ bị xóa bỏ khi container ngừng hoạt động. Bạn có thể lưu và đọc các dữ liệu từ hệ thống file, nhưng nếu vì lý do nào đó mà bạn cần khởi động lại container hoặc thay thế nó, các dữ liệu được ghi vào file sẽ bị mất vĩnh viễn.

Vì vậy, một chiến lược thích hợp cho các container là tạo ra các container ứng dụng không trạng thái (stateless). Nếu container của bạn chỉ có mã nguồn và không có dữ liệu, bạn có thể xóa nó đi và thay thế nó bằng một container mới mà không gặp trở ngại nào. Cách này giúp cho quá trình triển khai các container trở nên đơn giản hơn rất nhiều.

Nhưng điều này cũng dẫn đến hệ quả là chúng ta cần có một nơi để chứa dữ liệu bên ngoài các container chứa ứng dụng. Đây là điểm mạnh của hệ sinh thái Docker. Hệ thống danh bạ của Docker (Docker Container Registry) có một số lượng lớn các image. Trong phần trên, chúng ta đã sử dụng image của Python để làm image gốc cho ứng dụng Myblog. Ngoài ra, Docker cũng có image cho nhiều ngôn ngữ lập trình, cơ sở dữ liệu và các dịch vụ khác trong hệ thống danh bạ này. Hơn thế nữa, hệ thống này cũng cho phép các công ty xuất bản các container cho sản phẩm của họ, và ngay cả những người sử dụng bình thường như chúng ta cũng có thể xuất bản các image của chính mình trong đó nếu chúng ta muốn. Điều này có nghĩa là việc chúng ta cần làm để cài đặt các dịch vụ từ bên thứ ba chỉ gói gọn trong việc tìm kiếm các image thích hợp từ danh bạ và khởi động chúng với lệnh docker run và các tham số phù hợp.

Do đó, việc tiếp theo mà chúng ta sẽ làm là khởi tạo hai container mới, một cho cơ sở dữ liệu MySQL và một cho dịch vụ tìm kiếm Elasticsearch. Và sau đó, chúng ta sẽ dùng một lệnh rất dài để khởi động container cho ứng dụng Myblog và cho phép nó truy cập vào hai container này.

Khởi tạo container MySQL

Tương tự như các sản phẩm và dịch vụ khác, trong hệ thống danh bạ của Docker có nhiều image MySQL từ các nguồn khác nhau mà chúng ta có thể sử dụng. Và cũng như container Myblog, MySQL cần được khởi tạo với các biến môi trường trong lệnh docker run. Các biến này sẽ thiết lập mật mã, tên cơ sở dữ liệu … Và để tránh các rắc rối không cần thiết, chúng ta sẽ sử dụng image do chính đội ngũ phát triển MySQL cung cấp. Bạn có thể tham khảo thông tin chi tiết về image của MySQL từ trang thông tin trong danh bạ của Docker: https://hub.docker.com/r/mysql/mysql-server/.

Nếu bạn còn nhớ khối lượng công việc mà chúng ta phải thực hiện khi cài đặt MySQL trong Phần 17, bạn sẽ thấy Docker hữu ích thế nào khi bạn có thể triển khai MySQL dễ dàng hơn rất nhiều nhờ nó. Sau đây là lệnh docker run để khởi động một container MySQL:

Có một điểm lưu ý nhỏ trong trong ví dụ trên là chúng ta đã sử dụng MySQL phiên bản 5.7 chứ không phải các phiên bản mới hơn vì thư viện pymysql mà chúng ta đang sử dụng trong ứng dụng để giao tiếp với cơ sở dữ liệu có một vài vấn đề tương thích với các phiên bản MySQL mới.

Và chỉ cần thế thôi! Bạn có thể thi hành lệnh trên trên bất kỳ máy tính nào có cài đặt Docker và bạn sẽ nhận được một server MySQL sẵn sàng hoạt động với mật mã ngẫu nhiên, một cơ sở dữ liệu mới tinh tên là myblog, và một tài khoản cùng tên với quyền truy nhập đầy đủ đến cơ sở dữ liệu này. Lưu ý là bạn cần nhập một mật mã hợp lý cho biến môi trường MYSQL_PASSWORD trong lệnh trên.

Về phần ứng dụng, chúng ta cần cài đặt thư viện để truy xuất cơ sở dữ liệu MySQL như cách chúng ta đã làm khi triển khai ứng dụng trên Ubuntu. Ở đây, chúng ta vẫn sử dụng gói pymysql và đưa gói này vào Dockerfile:

Dockerfile: Thêm gói pymysql vào Dockerfile.

Và đừng quên rằng bất cứ khi nào bạn cập nhật mã nguồn ứng dụng hoặc Dockerfile, bạn cần tái tạo lại image cho ứng dụng với lệnh sau:

Đến đây, chúng ta có thể khởi động lại container Myblog, nhưng lần này sẽ có thêm tham số để khai báo địa chỉ của cơ cở dữ liệu. Nhờ đó, container Myblog có thể kết nối và truy cập cơ sở dữ liệu tại container MySQL:

Tùy chọn --link sẽ báo với Docker rằng container Myblog cần truy cập đến container MySQL và Docker sẽ thực hiện các công việc cần thiết để điều này xảy ra. Tham số đi kèm với tùy chọn này bao gồm hai phần và được phân biệt bằng dấu hai chấm. Phần đầu tiên là tên hoặc ID của container sẽ được liên kết với container ứng dụng, và trong trường hợp này, đó là tên của container MySQL chúng ta đã khởi tạo ở trên – cũng có giá trị là mysql. Phần thứ hai định nghĩa bí danh mà container Myblog có thể dùng để tham chiếu đến container MySQL. Ở đây, chúng ta sử dụng một tên tổng quát là dbserver để đại diện cho container MySQL.

Sau khi đã thiết lập liên kết với container MySQL, chúng ta sẽ gán giá trị thích hợp cho biến môi trường DATABASE_URL để SQLAlchemy có thể sử dụng cơ sở dữ liệu MySQL trong container của nó. Trong địa chỉ của cơ sở dữ liệu sẽ có bí danh dbserver mà chúng ta vừa khai báo để tham chiếu đến container MySQL, myblog cho tên của cơ sở dữ liệu và user để truy nhập, và cuối cùng là mật mã bạn đã chọn khi khởi động container MySQL.

Cũng cần lưu ý là container MySQL cần vài giây sau khi khởi động để có thể bắt đầu nhận các kết nối và truy vấn dữ liệu. Vì vậy, nếu bạn bắt đầu ứng dụng ngay lập tức sau khi khởi động container MySQL, lệnh flask db upgrade trong file boot.sh có thể báo lỗi vì không kết nối được với cơ sở dữ liệu. Để khắc phục vấn đề này, chúng ta sẽ thêm một vòng lặp vào boot.sh để thi hành lệnh cập nhật dữ liệu lần nữa nếu nó gặp lỗi trước đó:

boot.sh: Thử lại việc kết nối với cơ sở dữ liệu.

Vòng lặp này sẽ kiểm tra mã thoát (exit code) của lệnh flask db upgrade. Và nếu mã này khác với 0 thì xem như lệnh này đã gặp lỗi trong quá trình thi hành và sẽ tiến hành thử lại sau năm giây.

Khởi tạo container Elasticsearch

Tài liệu trực tuyến của Elasticsearch dành cho Docker cung cấp hướng dẫn chi tiết để khởi tạo container Elasticsearch trong chế độ đơn node (để sử dụng trong quá trình phát triển phần mềm) hoặc lưỡng node (để triển khai trong môi trường sản phẩm). Ở đây, chúng ta sẽ sử dụng thiết lập đơn node và dùng image chỉ có các các thành phần mã nguồn mở “oss” (open source engine). Chúng ta có thể khởi động container với lệnh sau:

Lệnh docker run ở trên có nhiều điểm tương đồng với các lệnh mà chúng ta đã dùng cho các container Myblog và MySQL trước đây. Tuy vậy vẫn có một số điểm khác nhau đáng lưu ý. Đầu tiên là hai tùy chọn - p đồng nghĩa với việc container này sẽ sử dụng hai cổng thay vì một như các ví dụ trước đây. Cả hai cổng 9200 và 9300 được ánh xạ sang các cổng cùng số hiệu trong máy chứa container.

Phần khác nhau còn lại nằm trong cú pháp để tham chiếu đến image của Elasticsearch. Từ trước đến giờ, chúng ta sử dụng một số cú pháp như sauđể tham chiếu đến các image:

  • <tên>:<thẻ>: dùng cho các image nội bộ trên máy (như là image cho Myblog)
  • <tên tài khoàn>/<tên>:<thẻ>: dùng cho các image nằm trên hệ thống danh bạ của Docker

Tuy nhiên, image của Elasticsearch lại nằm ngoài hai trường hợp trên. Do đó, chúng ta phải dùng cú pháp <danh bạ>/<tên tài khoản>/<tên>:<thẻ> với <danh bạ> là địa chỉ của danh bạ có chứa image này. Lý do là vì image của Elasticsearch cũng như một số các phần mềm khác không nằm trong hệ thống danh bạ của Docker mà có hệ thống danh bạ riêng của chúng. Trong trường hợp của Elasticsearch, danh bạ của nó ở tại địa chỉ docker.elastic.co thay vì trong hệ thống danh bạ được cung cấp bởi công ty Docker.

Sau khi container Elasticsearch đã được khởi tạo và hoạt động, chúng ta có thể cập nhật lệnh khởi động của container Myblog để tạo một liên kết đến Elasticsearch và thiết lập địa chỉ liên kết tương ứng:

Cũng như trước, bạn cần phải bảo đảm rằng container Myblog đã ngưng hoạt động trước khi thực hiện lệnh trên bằng cách sử dụng lệnh docker stop <ID-của-container-Myblog> nếu nó còn đang chạy. Ngoài ra, bạn cũng nên kiểm tra để chắc rằng các mật mã dùng cho MySQL và Elasticsearch là chính xác và được đặt đúng chỗ trong lệnh trên.

Sau khi hoàn tất quá trình này, bạn có thể sử dụng chức năng tìm kiếm tại địa chỉ http://localhost:8000. Nếu gặp lỗi, bạn có thể thử dò lỗi và sửa bằng cách kiểm tra nhật ký của container. Trong quá trình tìm lỗi, chắc chắn là bạn cần phải xem xét nhật ký của container Myblog để xem các lỗi từ mã nguồn Python:

Hệ thống danh bạ của Docker

Hiện giờ, chúng ta đã đóng gói toàn bộ ứng dụng trong các container của Docker, hai trong số chúng được tạo ra từ các image được cung cấp từ cộng đồng. Nếu bạn muốn cung cấp image của bạn cho người khác sử dụng, bạn cần phải đưa image của bạn lên hệ thống danh bạ của Docker qua một quá trình gọi là push.

Để có quyền truy cập vào hệ thống danh bạ của Docker, đầu tiên bạn phải vào Web site https://hub.docker.com và đăng ký tài khoản cho bạn. Bạn cần chọn một username thích hợp vì đây sẽ là username đi kèm với tất cả các image do bạn đưa lên.

Để truy cập vào tài khoản của bạn từ dòng lệnh, bạn cần đăng nhập vào Docker với lệnh docker login:

Nếu bạn thực hiện đúng các hướng dẫn trong bài từ đầu đến giờ, trên máy của bạn hiện có một image tên là myblog:latest. Để đưa image này lên hệ thống danh bạ của Docker, bạn cần thêm username của bạn với Docker vào tên của image này tương tự như với image của MySQL. Bạn có thể làm việc này với lệnh docker tag như sau:

Sau khi thực hiện lệnh này, nếu bạn liệt kê danh sách các image trong máy của bạn với lệnh docker images, bạn sẽ thấy hai mục khác nhau với tên Myblog: một image với tên gốc là myblog:latest, và một image mới hơn bao gồm tên tài khoản của bạn. Thật ra thì hai image này là một nhưng có hai bí danh khác nhau.

Để đưa image của bạn lên hệ thống danh bạ của Docker, hãy dùng lệnh docker push:

Sau bước này, mọi người có thể tải về và sử dụng image của bạn từ Docker. Bạn có thể viết các hướng dẫn để giúp người sử dụng tải về và khởi tạo container với image này tương tự như MySQL và các phần mềm khác trên Docker.

Triển khai các ứng dụng được đóng gói (containerized)

Ưu điểm lớn nhất của việc đóng gói ứng dụng với Docker là sau khi bạn đã thiết lập và thử nghiệm chúng trên máy của bạn, bạn có thể triển khai chúng trên bất kỳ nền tảng nào có hỗ trợ Docker. Ví dụ như bạn có thể dùng các máy chủ từ các dịch vụ như Digital Ocean, Linode hoặc Amazon Lightsail mà chúng ta đã nhắc đến trong Phần 17. Ngay cả các máy chủ với giá thuê bao thấp nhất trong các dịch vụ này cũng đủ khả năng để chạy Docker với một số các container.

Dịch vụ Container của Amazon (Amazon Container Service hay ECS) cho phép bạn tạo ra một cụm máy chủ để chạy container của bạn và được tích hợp toàn phần với môi trường AWS (Amazon Web Service), với các hỗ trợ để mở rộng (scaling) và cân bằng tải (load balancing) cũng như khả năng sử dụng các hệ thống danh bạ riêng cho các image của bạn. Và cuối cùng, một nền tảng điều phối container như là Kubernetes cung cấp khả năng tự động hóa rất cao trong việc sử dụng container. Các công cụ của Kurbenetes cho phép bạn thực hiện nhiều tác vụ phức tạp với container như triển khai nhiều container cùng lúc với hỗ trợ cân bằng tải, mở rộng, bảo mật, cập nhật và phục hồi ứng dụng chỉ bằng các khai báo trong các file văn bản theo định dạng YAML.

Chúng ta sẽ tạm ngừng ở đây. Hẹn gặp bạn trong phần tiếp theo.

3 thoughts on “Hướng dẫn lập trình Flask – Phần 19: Triển khai ứng dụng với Docker

  1. huy@huy-ThinkPad-T450s:~/techland$ sudo docker images
    REPOSITORY TAG IMAGE ID CREATED SIZE
    b34a6b722d4e 18 seconds ago 337MB
    ubuntu 18.04 775349758637 6 weeks ago 64.2MB

    tạo images không có tên mà để là sao bạn?

    1. Có thể có nhiều lý do dẫn đến kết quả này. Một trong các lý do là máy của bạn có sẵn các image và container có lỗi trước khi tạo image và container trong phần này. Bạn nên xóa bỏ các image và container hiện tại, khởi động lại Docker service trên máy của bạn rồi bắt đầu tạo lại container cho bài này nhé.

  2. copy lệnh tạo file image trong bài bạn tạo được file images nhưng không có tên cũng như thẻ tag là để là

Leave a Reply to Huy Cancel reply

Your email address will not be published. Required fields are marked *