+300

Docker: Chưa biết gì đến biết dùng (Phần 2 - Dockerfile)

1. Mở đầu

Xin chào các bạn, ở bài trước Docker: Chưa biết gì đến biết dùng (Phần 1):

  • Chúng ta đã cùng nhau tìm hiểu công nghệ ảo hóa containerlization với công cụ docker của công ty cùng tên. Tuy nhiên nội dung chủ yếu tập trung vào lý thuyết vào lịch sử và cơ chế hoạt động.

  • Hôm nay, chúng ta sẽ đi sâu hơn để khám phá cách sử dụng Docker trong thực tế.

  • Như bác Hoàng Nam Tiến từng nói:

    Hãy nhớ rằng Tiếng Anh là ngôn ngữ để làm việc , để sống, không còn là ngoại ngữ nữa.
    
  • Thì Docker hiện tại gần như đã trở thành tiêu chuẩn trong phát triển phần mềm, không chỉ xuất phát từ sự tiện lợi, mà còn bởi cách nó thay đổi tư duy, quy trình và phương pháp làm việc.

  • Nên nếu như bạn chưa dùng Docker thì câu hỏi không phải là Tại sao nữa mà là Làm thế nào để bắt đầu. Chuỗi bài viết này sẽ hỗ trợ bạn giải quyết phần nào câu hỏi đó.

  • Trước khi đọc phần 2, đừng quên đọc lại phần 1 - để mình hiểu rõ lý thuyếtbản chất của Docker đã nhé.

Let's go ! 👋👋👋

2. Source code

Bớt lý thuyết lại, coding luôn:

Dưới đây là cấu trúc demo source code của chúng ta:

image.png

  • Thư mục web_app:

    • Đây là nơi chứa mã nguồn chương trình của bạn.

    • Nó có thể là một ứng dụng viết bằng PHP, Node.js hoặc Python ...

    • Trong ví dụ này, để cho đơn giản, ta chỉ tạo file index.html với nội dung:

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Docker tutorial</title>
      </head>
      <body>
          <div>Hello World</div>
      </body>
      </html>
      

      Sẽ hiển thị dòng Hello World khi chạy trên trình duyệt.

    • Trong quá trình phát triển, bạn có thể chỉnh sửa mã nguồn trong thư mục này trực tiếp trên máy thật (host OS) bằng các trình soạn thảo như VS Code.

    • Nhờ docker volume, những thay đổi sẽ ngay lập tức được cập nhật vào bên trong container, kết hợp với hot reload để hiển thị kết quả tức thì. Ví dụ sửa text Hello World thành Hello World From Asia.

  • Dockerfile:

    • Như đã tìm hiểu, file này định nghĩa cách tạo docker image.
    • Sau khi build được image, bạn có thể khởi tạo container - đây chính là môi trường ảo hoá để chạy ứng dụng ở bên trong.
  • File start.sh:

    • Đây là script chứa những câu lệnh được thực thi mỗi khi container khởi động.
    • Có thể dùng script này để khởi chạy các dịch vụ như MySQL, Nginx, Redis, ...

3. Dockerfile là gì ?

  • Dockerfile:

    • Một file dạng text thuần tuý.
    • Không có đuôi mở rộng (như .txt, .sh ...) mà docker nhận diện nó thông qua tên Dockerfile cố định.
    • Gồm các lệnh được viết tuần tự, hướng dẫn Docker xây dựng docker image mà bạn muốn.
  • Có thể liên tưởng tới việc cài lại hệ điều hành:

    • Khi cài lại Windows:
      • Bạn dùng usb boot để cài os lên laptop.
      • Sau đó cài các phần mềm lên như Chrome, VS code, ...
    • Thì khi build docker images từ Dockerfile cũng tương tự:
      • Bạn cũng bắt đầu từ một base image ví dụ: ubuntu, centos ...

      • Sau đó thêm các lệnh cài đặt, sao chép hoặc cấu hình.

         # Use the official Ubuntu image as the base
         FROM ubuntu:latest
        
         # Configure the main working directory
         WORKDIR /app
        
         # Update package lists and install necessary packages
         RUN apt-get update; \
             apt-get install -y --no-install-recommends \
                 nginx \
                 nano \
                 vim \
                 postgresql; \
             apt-get clean; \
             rm -rf /var/lib/apt/lists/*
        
         # Copy the startup script and set permissions
         COPY start.sh .
         RUN chmod +x ./start.sh
        
         # Set the entrypoint
         ENTRYPOINT ["./start.sh"]
        
         # Expose the required port
         EXPOSE 80
        
        
    • Như việc xây một bức tường:

image.png

4. Cách viết Dockerfile

Các câu lệnh trong Dockerfile được liệt kê và giải thích trên trang chủ cũng khá đầy đủ

Sau đây chúng ta sẽ điểm qua những câu lệnh nào hay sử dụng nhất.

4.1 Thiết lập thông tin cơ bản

  • FROM

    • Ví dụ:

      FROM centos:7
      FROM ubuntu:16.04
      FROM ubuntu:22.04
      ...
      FROM ubuntu:latest
      
    • Câu lệnh này luôn nằm đầu tiên, nó giúp chúng ta chỉ định image ta đang muốn tạo khởi nguồn từ đâu.

    • Hiểu nôm na là bắt nguồn từ ubuntu, hay centos ... cùng phiên bản của nó (latest có nghĩa là phiên bản mới nhất) sau đó cài thêm những gì lên.

    • Có thể bạn đã biết, Docker hub - nơi lưu trữ và chia sẻ các image sẽ chứa những image gốc này.

    • Khi Docker đọc tới câu lệnh này, nó sẽ tự động tìm xem image ubuntu:16.04 này đã tồn tại trong host OS chưa, nếu chưa thì Docker sẽ tự động pull image này về. Trong đó ubuntu là tên của image, 16:04 là tag, latest nghĩa là tag mới nhất.

  • WORKDIR

    • Ví dụ:

      WORKDIR /app
      WORKDIR /path/to/workdir
      
    • Thay đổi thư mục làm việc

4.2 Cài thêm ứng dụng

Bây giờ, chúng ta sẽ cài thêm các ứng dụng, thiết lập môi trường cần thiết trên ubuntu:16.04 này

Bạn có thể cài nginx, php, python, ruby, java ... phụ thuộc vào nhu cầu của bạn, sử dụng:

  • RUN : Để thực thi một câu lệnh nào đó trong quá trình build images.

  • CMD : Để thực thi một câu lệnh trong quá trình bật container.

    Mỗi Dockerfile chỉ có một câu lệnh CMD, nếu như có nhiều hơn một câu lệnh CMD thì chỉ có câu lệnh CMD cuối cùng được sử dụng.

    Một câu hỏi đặt ra là nếu tôi muốn khởi động nhiều ứng dụng khi start container thì sao, lúc đó hay nghĩ tới ENTRYPOINT

  • ENTRYPOINT: Để thực thi một số câu lệnh trong quá trình start container, những câu lệnh này sẽ được viết trong file .sh.

Ví dụ:

# Update ubuntu
RUN apt-get update

# Install nginx
RUN apt-get install -y nginx

# Install mysql server
RUN echo "mysql-server mysql-server/root_password password root" | debconf-set-selections \
    && echo "mysql-server mysql-server/root_password_again password root" | debconf-set-selections \
    && apt-get install -y mysql-server

Trong khi cài nginx, sẽ có câu hỏi xuất hiện và bạn cần trả lời yes/no, khi đó tùy chọn -y trong RUN apt-get install -y nginx sẽ thể hiện cho sự lựa chọn yes của bạn.

4.3 Cấu hình

  • EXPOSE: Container sẽ lắng nghe trên các cổng mạng được chỉ định khi chạy

  • ADD : Copy file, thư mục, remote file thêm chúng vào filesystem của image.

  • COPY : Copy file, thư mục từ host machine vào image. Có thể sử dụng url cho tập tin cần copy.

  • WORKDIR : Định nghĩa directory cho CMD

  • VOLUME : Mount thư mục từ máy host vào container.

Tạo file .sh

Như ở phần entrypoint đã nói, cho dù chỉ cần một câu lệnh mình vẫn dùng ENTRYPOINT, để sau này dễ dàng tùy biến, phát triển.

Tạo file start.sh như sau

#!/bin/bash
service nginx start
exec $@

Ta có ví dụ ở phần này như sau:

ADD start.sh /venv

WORKDIR /venv

RUN chmod a+x /venv/*

ENTRYPOINT ["/venv/start.sh"]

EXPOSE 80

Tổng hợp lại, ta có một ví dụ cho Dockerfile như sau :

FROM ubuntu:16.04

MAINTAINER HoanKi<hoanki2212@gmail.com>

RUN DEBIAN_FRONTEND=noninteractive

RUN apt-get update

RUN apt-get install -y nginx

RUN echo "mysql-server mysql-server/root_password password root" | debconf-set-selections \
    && echo "mysql-server mysql-server/root_password_again password root" | debconf-set-selections \
    && apt-get install -y mysql-server

WORKDIR /venv

COPY start.sh /venv

RUN chmod a+x /venv/*

ENTRYPOINT ["/venv/start.sh"]

EXPOSE 80
  • Tạo file hello.html trong thư mục webroot:
<h1>Hello word</h1>

5. Cách sử dụng Dockerfile

5.1 Build docker image từ Dockerfile

Ta sử dụng câu lệnh sau:

sudo docker build -t <image_name> .

Ví dụ:

sudo docker build -t ubuntu-nginx . 

Bạn có thể dùng lệnh

docker images

để xem thành quả nhé !

5.2 Tạo container từ image.

  • Gõ lệnh theo syntax:

sudo docker run -v <forder_in_computer>:<forder_in_container> -p <port_in_computer>:<port_in_container> -it <image_name> /bin/bash

Trong đó:

  • -v : Thể hiện việc mount volume, dữ liệu từ thư mục từ máy thật có thể được truy cập từ thư mục của máy ảo.

  • -p: Cổng mạng từ máy thật để dẫn tới cổng mạng của máy ảo đang chạy.

  • -t: Chạy container và mở terminal bằng /bin/bash

  • Ví dụ vào localhost mặc định của nginx:

sudo docker run -p 9000:80 -it ubuntu-nginx /bin/bash

Kiểm tra log trên Terminal:

Trên trình duyệt:

  • Ví dụ vào thư mục dự án ở máy thật:
sudo docker run -v  /media/hoanki/PROJECT4/GitRepo/docker_tutorial/webroot:/var/www/html -p 9000:80 -it ubuntu-nginx /bin/bash

Thay thế /media/hoanki/PROJECT4/GitRepo/docker_tutorial/webroot cho đúng với trên máy bạn nhé !

Kết quả:

6. Cách chia sẻ Dockerfile

Docker-hub: Nơi lưu trữ và chia sẻ các image của Docker, nhưng không chỉ có vậy.

Nãy giờ bạn và tôi đang build image hoàn toàn dưới local, nhưng Docker Hub còn hỗ trợ chúng ta làm việc này trên server nữa.

  • Mình tạo mới repo docker-basic trên Github, như này:

  • Sau đó mình tạo mới một repo trên Dockerhub.

Vào Create chọn Create Automated Build, chọn Github rồi trỏ tới docker-basic bạn vừa tạo ở GitHub.

Và ta có docker_basic, trông như sau:

  • Dockerhub sẽ hỗ trợ bạn build docker image online, sau đó bạn có thể pull nó về để sử dụng.

  • Vào tab Build Settings,

  • Từ giờ trở đi, mỗi khi bạn push code lên branch nào trên github branch đó bạn đã setting trên DockerHub thì images sẽ tự động được build.

Việc build này sẽ tự động thực hiên trên server Docker Hub nhé, ví dụ mình push code lên branch init_dockerfile thì Dockerhub tự động build image, kết quả có ở trong tab Build Detail.

Mình tạo pull request, sau khi mình merge nó thì DockerHub cũng sẽ build image tiếp trên branch master.

  • Thử đi nhé, đôi khi buid ở local thì pass mà build trên server thì lỗi, cảm giác fix mãi nó mới hiện dòng chữ "Success" nó mới awesome làm sao.

###############################################

😄 😄 😄 Thank for your attention 😄 😄 😄

😄 😄 😄 Mình đã viết xong Docker: Chưa biết gì đến biết dùng (Phần 3), mời các bạn đọc tiếp. 😄 😄 😄


All rights reserved

Bình luận

Đăng nhập để bình luận
Avatar
@yendevy
thg 4 14, 2018 1:23 CH

Thật sự là phải tạo tk để vào vote cho bài viết này, thank chủ thớt 😄

Avatar
@HoanKi
thg 4 15, 2018 8:35 SA

Hehe, mình cảm ơn bạn nhìu nhìu ná =))))))

Avatar
@nqthai95
thg 5 28, 2018 3:45 SA

Cùng ý với pác trên. Phải tạo tk để commend :d Cảm ơn chủ thớt nhiều. Hy vọng có bài mới. Rất thực tiễn, mỗi lần sang project khác là cả đống setup mới conflict điên loạn.

Avatar
@hieunt
thg 6 3, 2018 10:57 SA

Anh oi cho em hoi .. Em muon link image mysql vao image ubuntu thay vi RUN cmd trong Dockerfile duoc khong a ?

Avatar
@HoanKi
thg 10 10, 2018 5:32 CH

Có bạn nhé, mình sẽ dùng docker-compose bạn ạ, trong phần 3 mình sẽ hướng dẫn.

Avatar

mình muốn sử dụng sql server trên ubuntu thì docker có xử lí được không :>

Avatar
@HoanKi
thg 10 10, 2018 5:33 CH

Có bạn ạ, theo như hướng dẫn trong phần 2 này là đã sử dụng mysql server trên ubuntu được rồi đó.

Avatar
@chithien175
thg 10 26, 2018 7:42 SA

Cảm ơn tác giả rất nhiều Mong bác giữ vững phong độ để tiếp tục các phần tiếp theo Hóng phần sau 😃

Avatar
@HoanKi
thg 10 26, 2018 4:00 CH

Cảm ơn comment của bạn nhé ^_^

Avatar
@AnhPta
thg 11 9, 2018 9:40 SA

E hóng phần 3 của bác mà mãi chưa thấy đâu 😃))

Avatar
@HoanKi
thg 11 9, 2018 12:13 CH

Mình cũng đang viết phần 3 rồi bạn nhé.

Avatar
@duypv98
thg 1 2, 2019 11:19 SA

Cảm ơn bác rất nhiều

Avatar
@ddthanhdat
thg 2 11, 2019 11:43 SA

Mình có 3 dự án chạy trên 3 cổng khác nhau. Các dự án là node js dùng chung thư viện, vậy giờ phải tạo 3 docker. Như vậy làm nặng thêm vps không bạn.

Xem thêm (3)
Avatar
@ddthanhdat
thg 2 13, 2019 8:00 SA

Mình có 3 dự án chạy liên tục:

  • Laravel
  • Node js chat bot telegram
  • Node js chat bot facebook Cùng kết nói với 1 mysql và kết nói với redis server(local)

Mình ko tìm cách nào chia nhỏ ra để ứng dụng docker.

Avatar
@HoanKi
thg 2 13, 2019 5:28 CH

Câu trả lời nằm ở docker-compose nhé, nó giúp bạn tách biệt ra từng service riêng, mỗi container sẽ chạy 1 service ! Bạn khởi động container databseredis mà dùng chung lên, ví dụ:

docker-compose up -d mysql 
docker-compose up -d redis

Sau đó bật các container mà dùng riêng cho cho mỗi dự án, chúng sẽ kết nối tới chung tới databaseredis ở trên kia.

docker-compose up laravel
docker-compose up node_js_telegram
docker-compose up node_js_facebook

Mình đang cập nhật tiếp phần 3 và viết phần 4 để hướng dẫn triển khai docker theo cách này ở mức đơn giản hơn.

Avatar

Bạn ơi Link phần 3 bị lỗi à? Và cho mình hỏi khi nào có phần 4 vậy 😃

Avatar
@HoanKi
thg 2 26, 2019 9:08 SA

Mình có save bài viết phần 3 vào Drafts vài ngày để cập nhật thêm một số mục, bây giờ thì bài viết đã public rồi bạn nhé !

Avatar
@phungtd
thg 2 26, 2019 2:53 SA

Bài viết rất hay, xin cảm ơn nhiều! Link phần 3 hỏng rồi. ad cho xin lại link phần 3 với. thanks!

Avatar
@HoanKi
thg 2 26, 2019 9:08 SA

Mình có save bài viết phần 3 vào Drafts vài ngày để cập nhật thêm một số mục, bây giờ thì bài viết đã public rồi bạn nhé !

Avatar

Tuyệt vời

Avatar
@lhngoc2497
thg 4 14, 2019 3:28 CH

cảm ơn thớt đã viết bài này!hay quá :V

Avatar
@pvtamh2bg
thg 5 9, 2019 10:00 SA

bài viết hay. Thank bạn nhiều

Avatar
Avatar

Thanks Bác.

Avatar
@diennv
thg 12 14, 2019 6:31 CH

đọc bằng tiếng anh mình không thể hiểu hết được, vì kiến thức còn bị hổng, tiếng anh yếu. Nhờ bài này mà hiểu được rõ hơn. Thank bác thớt

Avatar

xin lỗi, hình như ADD mới cho phép URL và giải nén file tar?

Avatar
@ho.xuan.vinh
thg 2 11, 2020 10:15 SA

Mình làm tới Kubernetes luôn đi bạn ơi. Bài hay thiệt ak.

Avatar
@ducanh6893
thg 4 10, 2020 3:56 SA

Mình sử dụng window để build container, khi chạy câu lệnh "docker run -v D:/Docker/webroot:/var/www/html -p 9000:80 -it ubuntu-nginx /bin/bash" kết quả là success. Nhưng khi vào local host thì hiện lỗi 403, Forbiden. Mình hiểu có thể lỗi do permission của file helloworld.html trong thư mục /var/www/html không được set quyền cho user của nginx là www-data, nhưng khi mình chạy câu lệnh "chown -R www-data:www-data hello.html" thì file vẫn không đổi permission. Không biết là mình hiểu sai lỗi hay bạn có cách nào khắc phục không ạ? Cảm ơn

Avatar
@hayquen
thg 4 18, 2020 8:30 SA

mình cũng dính lỗi tương tự, hóng solution xD

Avatar
@lehaiquantb
thg 12 11, 2020 10:43 SA

cho ehỏi tham số -v ở docker run và VOLUME ở DockerFile là cùng 1 chức năng ạ??

Avatar
@Binne
thg 12 23, 2020 2:52 SA

đúng rồi đấy bạn

Avatar
@kieubaoduy1412
thg 1 26, 2021 7:49 SA

quá tuyệt vời

Avatar
@huangtheng
thg 4 8, 2021 4:51 SA

Hay quá. VN rất cần những người như bạn !!!😀😀😀😀

Avatar
@huyhoang5804
thg 6 23, 2021 11:54 CH

RUN DEBIAN_FRONTEND=noninteractive Dòng này để làm gì bạn nhỉ

Avatar
@huyhoang5804
thg 6 24, 2021 6:17 CH

Ví dụ như ban đầu mình build 1 image, sau mình muốn setup thêm 1 số thứ thì mình sửa lại docker file và build lại à bạn, vậy thì nó sẽ lại tạo image mới à.

Avatar
@huyhoang5804
thg 7 1, 2021 9:54 CH

sao bên trên bạn dùng ADD start.sh /venv, xuống dưới lại dùng COPY start.sh /venv vậy?

Avatar
@nghiadev95
thg 9 4, 2023 4:03 CH
Avatar
+300
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í