Series về Docker trong thực thành & sản xuất - Dockerfile để tạo một image

Series về Docker trong thực thành & sản xuất - Dockerfile để tạo một image

Những mẩu tin ngắn hàng ngày dành cho bạn
  • Puppeteer thì chắc ai cũng biết rồi - Một thư viện JavaScript cung cấp API cấp cao để điều khiển Chrome hoặc Firefox. Thường thì nó sẽ mở trình duyệt trên máy tính để chạy tác vụ tự động. Cơ mà mới đây steel-browser được giới thiệu để đưa Puppeteer lên đám mây. Tức là thay vì mở trình duyệt trên máy thì mở trình duyệt trên đám mây.

    Cách làm cũng đơn giản, chỉ cần sửa lại một chút đoạn khởi chạy:

    const browser = await puppeteer.launch({...});

    Thành:

    const browser = await puppeteer.connect({ browserWSEndpoint: 'wss://connect.steel.dev?apiKey=MY_STEEL_API_KEY', });

    À phiên bản miễn phí thì đang giới hạn 100 giờ chạy mỗi tháng á các bạn 😄

    » Xem thêm
  • Đây! Một vấn đề mà từ xưa đến nay mình cứ thắc mắc mãi, và cho đến hôm qua thì mọi thứ đã sáng tỏ.

    Bình thường mọi người dùng height: 100vh để đặt chiều cao bằng với viewport của màn hình. Trên máy tính thì không vấn đề gì, thậm chí giả lập kích thước của điện thoại thông minh thì mọi thứ vẩn ổn. Nhưng khi mở trên điện thoại thì height 100vh lúc nào cũng vượt quá viewport. Ủa!? Là sao???

    Lý giải cho điều này là do trên thiết bị di động có cách tính viewport khác với máy tính. Nó thường bị can thiệp hay ảnh hưởng bởi thanh địa chỉ, thanh điều hướng của nền tảng mà bạn đang sử dụng. Vậy nên nếu muốn 100vh trên di động đúng bằng viewport thì cần phải làm thêm một bước thiết lập lại viewport.

    Dễ lắm, đầu tiên cần tạo một css variable --vh ở ngay thẻ script đầu trang.

    function updateViewportHeight() { const viewportHeight = globalThis.innerHeight; document.documentElement.style.setProperty('--vh', `${viewportHeight * 0.01}px`); } updateViewportHeight(); window.addEventListener('resize', updateViewportHeight);

    Sau đó thay vì dùng height: 100vh thì chuyển thành height: calc(var(--vh, 1vh) * 100). Thế là xong.

    » Xem thêm
  • Cả ngày hôm nay mình dành thời gian để làm giao diện tiếp thị cho gói hội viên của 2coffee.dev. Vậy là cuối cùng thì cũng chính thức đi vào vào con đường mà 5 năm trước cũng không ngờ đến được: "Bán một cái gì đó". Người ta thường nói "Cho đi để nhận lại", bên cạnh đó cũng có câu "Nếu giỏi một cái gì đó, đừng làm nó miễn phí". Nếu theo dõi đủ lâu, bạn đọc sẽ thấy chẳng có gì mình giấu giếm. Biết gì viết nấy, và đôi khi nhờ viết ra mà nhận lại được sự góp ý của độc giả. Từ đó giúp mình hoàn thiện bản thân nhiều hơn.

    Membership là tính năng mà mình sắp sửa giới thiệu. Trở thành hội viên của blog, bạn sẽ có một số đặc quyền nhất định, ví dụ như truy cập vào các bài viết chỉ dành riêng cho hội viên. Các bài viết này về các chủ đề chuyên sâu và được hệ thống hoá sao cho dễ đọc và dễ nắm bắt nhất. Qua đó cung cấp thêm nhiều kiến thức và trau dồi kỹ năng cho bạn đọc.

    Để đạt được đến ngày hôm nay là công rất lớn của các bạn đọc giả, của những người yêu mến 2coffee.dev. Nhờ các bạn mà blog mới có ngày hôm nay. Bên cạnh đó, bản thân mình cũng phải thay đổi liên tục, phải vượt ra khỏi vùng an toàn, làm những điều mà trước nay không dám. Dù sao đi nữa thì đây cũng mới là khởi đầu cho mọi sự gian nan. Nhưng đừng bao giờ nản nha các bạn ơi 😄

    » Xem thêm

Image

Như tôi đã đề cập ở bài viết trước, image là file để tạo ra container, cũng là file cần phải được tạo (build) ra để chạy ứng dụng của bạn. Để build một image ta dùng lệnh:

docker build -t <image> <path>
  • -t là cờ gắn tên cho image.
  • path là đường dẫn đến thư mục chứa Dockerfile.

Tên của image thường có 3 phần: registry/name:tag.

Trong đó registry là resource để pull/push image, name là tên của image và tag thường là các version. Nếu registry không được chỉ định docker sẽ pull từ docker hub, còn nếu tag không được chỉ định docker sẽ lấy tag mặc định là latest.

Để pull một image ta dùng lệnh docker pull, để push một image ta dùng lệnh docker push, để gắn tag cho một image có sẵn trong máy dùng lệnh docker tag, xem danh sách những image có trong máy dùng lệnh docker image ls:

# pull image nginx version 2.1
docker pull nginx:2.1

# push nginx lên registry của estacks
docker tag nginx:2.1 registry.estacks.icu/nginx:2.1
docker push registry.estacks.icu/nginx:2.1

# xem danh sách image trong máy
docker image ls

Tham khảo thêm các lệnh docker build, docker image.

Dockerfile

Hầu như các image mà chúng ta tạo ra đều base trên một image khác, bắt đầu thường là như thế này:

FROM node:12

Các ứng dụng được viết hoặc sử dụng trên nền tảng nào hầu như đều có các image là base ở trên trang của docker hub.

Tôi lấy ví dụ như node.js thì có node, mysql thì có mysql

Vì docker hub là một registry và người dùng bình thường cũng có thể tải lên image của họ ở trên này. Có một cách để nhận biết các official image (tức là những image chính chủ của nhà cung cấp) là nó có tiền tố _ trong liên kết dẫn đến image đó.

Bạn cũng có thể tự tạo được những base image cho riêng mình nếu như bạn thực sự có lý do để làm điều đó (!?).

Tóm lại, tư tưởng để build một image có thể chạy được app của bạn ở đây là base nó trên một image gốc phù hợp với nền tảng mà app đang sử dụng.

Các lệnh cơ bản trong Dockerfile

FROM

FROM là lệnh hướng dẫn xây dựng một image từ image base. Một dockerfile phải bắt đầu bằng lệnh FROM tiếp sau đó là tên của image. Tên image là bất kì tên nào hợp lệ mà docker có thể pull về từ registry hoặc là có sẵn ở trong máy chủ.

Ví dụ:

FROM node:12

Tham khảo thêm FROM.

COPY

COPY là lệnh sao chép file/folder từ local vào bên trong image. Hầu hết bạn luôn cần lệnh này vì source code của bạn phải được copy vào trong image thì mới có cái để chạy chứ đúng không.

COPY <src> <dest>

## Ví dụ copy thư mục /User/hoaitx/src/my-app vào thư mục /src/my-app trong image:  
COPY /User/hoaitx/src/my-app /src/my-app

dest phải là một đường dẫn tuyệt đối, hoặc nếu chỉ định WORKDIR có thể dùng dest là đường dẫn tương đối.

WORKDIR /src

COPY /User/hoaitx/src/my-app my-app

Tham khảo thêm COPY.

WORKDIR

WORKDIR thiết lập thư mục làm việc hiện tại cho các lệnh RUN, CMD, ENTRYPOINT, COPYADD.

Tức là thiết lập WORKDIR ở thư mục nào thì con trỏ lệnh sẽ ở thư mục đó.

WORKDIR /src

RUN pwd

Kết quả sẽ là /src.

Tham khảo thêm WORKDIR.

VOLUME

VOLUME là lệnh để thiết lập một điểm gắn kết giữa máy chủ docker với container.
Khi một container được khởi tạo và chạy thành công thì mọi dữ liệu mà nó tạo ra và lưu trữ sẽ nằm ở trong container. Khi container bị destroy toàn bộ dữ liệu đó sẽ bị mất.
Tưởng tượng khi bạn start một mysql. Nhưng nếu lỡ destroy container thì mọi dữ liệu sẽ bị mất, lúc đó bạn sẽ cần đến VOLUME giúp cho việc map folder dữ liệu trong container ra ngoài local.

Ví dụ với mysql thì folder chứa dữ liệu là /data, thì trước khi start hãy map nó với thư mục /User/hoaitx/mysql/data ở local.

VOLUME /User/hoaitx/mysql/data /data

Thế là xong, lúc này bạn có destroy hoặc start lại thì dữ liệu vẫn an toàn ở thư mục local kia của bạn.

Tham khảo thêm VOLUME.

EXPOSE

EXPOSE được sử dụng như một loại tài liệu để người dùng Dockerfile biết được ứng dụng của bạn thực sự hoạt động ở port nào thay vì họ phải tự đoán. Điều này giúp cho việc expose port lúc chạy container.

# Giả sử app my-nginx của tôi config là chạy ở port 8080
EXPOSE 8080

Khi đó tôi sẽ biết để map port 8080 ra 8081 ở máy chủ docker của tôi:

docker run --name my-nginx -p 8081:8080 my-nginx

Tham khảo thêm EXPOSE.

ENTRYPOINT

ENTRYPOINT cho phép bạn cấu hình một lệnh sẽ luôn được thực thi khi khởi chạy một container.

ENTRYPOINT ["executable", "param1", "param2"]

Trong đó executable là một lệnh hoặc một tập lệnh nhị phân, ví dụ như bash, node

Ví dụ để chạy ứng dụng node.js của tôi thì mỗi khi khởi chạy container tôi phải chạy lệnh node server.js:

ENTRYPOINT ["node", "server.js"]

Tham khảo thêm ENTRYPOINT.

CMD

CMD cung cấp các giá trị mặc định cho một container khi được khởi chạy. Trong một Dockerfile nếu có nhiều CMD thì CMD cuối cùng được sử dụng.

CMD ["executable", "param1", "param2"]

Ví dụ mỗi khi khởi chạy container tôi cần dùng lệnh node server.js để khởi động server:

CMD ["node", "server.js"]

Tham khảo thêm CMD.

Đến đây nếu các bạn để ý thì ENTRYPOINT và CMD có cách dùng sao giống nhau vậy? Nó đều được thực thi mỗi khi container được khởi tạo. Nó cũng có executable và các params?

Thì hãy quay trở lại với mục đích sử dụng của từng lệnh, "CMD cung cấp các giá trị mặc định" tức là CMD có thể không cần có "executable", khi CMD không có executable thì bắt buộc phải có ENTRYPOINT để chỉ định executable. ENTRYPOINT thì ngược lại, executable là bắt buộc. Việc kết hợp ENTRYPOINT & CMD sẽ tạo ra image phù hợp với mục đích sử dụng của bạn.

Ví dụ về việc kết hợp cả 2 ENTRYPOINT & CMD để tạo ứng dụng node.js của tôi:

ENTRYPOINT ["node"]
CMD ["index.js"]

Để rõ ràng hơn, nếu bạn muốn tạo một image dành riêng cho một lệnh cụ thể thì bạn sẽ sử dụng ENTRYPOINT. Ngược lại nếu bạn muốn tạo một image cho mục đích chung thì có thể sử dụng CMD.

Ngoài những lệnh cơ bản mình trình bày ở trên còn một số lệnh nữa, bạn có thể tham khảo chi tiết ở đây.

Hãy bắt tay vào tạo một image đầu tiên

Bây giờ, hãy vận dụng hết những thứ tôi chia sẽ bên trên để phân tích xem tôi đã làm gì trong Dockerfile này nhé.

FROM node:12

ENV NODE_ENV=production

WORKDIR /app

COPY ["package.json", "package-lock.json*", "./"]

RUN npm install --production

COPY . .  

EXPOSE 3000

CMD [ "node", "server.js" ]

Build:

docker build -t registry.estacks.icu/my-node:1.0.0 .  

Run:

docker run -d -p 3000:3000 my-node:1.0.0

Tổng kết

Hầu hết chúng ta đều tạo ra một image dựa trên một image khác. Các image được tạo ra bằng Dockerfile chứa những câu lệnh chỉ dẫn cách mà ứng dụng của bạn được xây dựng. Tên của image thường chứa 3 yếu tố: <registry>/<name>:<tag>, nếu <registry> không được chỉ định mặc định docker sẽ pull từ docker hub, còn nếu tag không được chỉ định thì docker hiểu mặc định sẽ là latest.

Cao cấp
Hello

Tôi & khao khát "chơi chữ"

Bạn đã thử viết? Và rồi thất bại hoặc chưa ưng ý? Tại 2coffee.dev chúng tôi đã có quãng thời gian chật vật với công việc viết. Đừng nản chí, vì giờ đây chúng tôi đã có cách giúp bạn. Hãy bấm vào để trở thành hội viên ngay!

Bạn đã thử viết? Và rồi thất bại hoặc chưa ưng ý? Tại 2coffee.dev chúng tôi đã có quãng thời gian chật vật với công việc viết. Đừng nản chí, vì giờ đây chúng tôi đã có cách giúp bạn. Hãy bấm vào để trở thành hội viên ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.
Author

Xin chào, tôi tên là Hoài - một anh Dev kể chuyện bằng cách viết ✍️ và làm sản phẩm 🚀. Với nhiều năm kinh nghiệm lập trình, tôi đã đóng góp một phần công sức cho nhiều sản phẩm mang lại giá trị cho người dùng tại nơi đang làm việc, cũng như cho chính bản thân. Sở thích của tôi là đọc, viết, nghiên cứu... Tôi tạo ra trang Blog này với sứ mệnh mang đến những bài viết chất lượng cho độc giả của 2coffee.dev.Hãy theo dõi tôi qua các kênh LinkedIn, Facebook, Instagram, Telegram.

Bạn thấy bài viết này có ích?
Không

Bình luận (1)

Nội dung bình luận...
Avatar
Phan Tung3 năm trước
bài viết rất bổ ích, cảm ơn tác giả
Trả lời
Avatar
Xuân Hoài Tống3 năm trước
Cảm ơn bạn, nhớ ghé thăm blog thường xuyên nhé