Về những cái layer của docker image

Ngắn gọn về docker image, là nơi chứa các tập lệnh để docker chạy cái container, thường thì một docker images này sẽ dựa trên docker images khác. Ví dụ như mình tạo cái image ubuntu-snort có cài thêm snort dựa trên image gốc là ubuntu pull về ở bài trước ấy.

Ở bài trước mình có tạo docker image bằng cách chạy container từ image gốc lên rồi cài từa lưa vào, sau đó commit ra cái image mới. Cách này trong những trường hợp làm nhanh lẹ và lười như mình thì được, còn làm một cách đàng hoàng thì né nó ra. Chơi kiểu này thì khó mà sửa đổi lệnh và có thêm một số vấn đề, ví dụ giờ chạy apt update, rồi apt install wget, sau đó docker commit ra image mới là ubuntu-wget , ta sẽ có:

Như vậy ta thấy kích thước của image mới đã tăng lên. Bây giờ vì một số lý do nào đó ta vào gỡ cái wget đi.

docker run -it ubuntu-wget

apt remove wget

Wtf, cái image nó lại tăng size lên, vấn đề ở chỗ này, dùng docker history ubuntu-wget để xem thế nào

Đây, cài wget vào nó tăng size, gỡ ra cũng tăng thêm size =)). Cứ thế này thì càng thao tác thì cái image nó càng bự :v. Đây là 1 nhược điểm, hơn nữa dùng cách này để tạo image thì mình không track được những thay đổi mình thực hiện với image. À, lý do mà nó tăng size lên thì xíu nói, còn cách này tạm thời thì lơ nó đi.

Ở lẽ thông thường, người ta build 1 docker image thì họ dùng Dockerfile, chứ không ai đi dùng kiểu trên cả. Đơn giản thì Dockerfile là file text chứa các khai báo và lệnh cần thiết để docker chạy khi build một image theo yêu cầu của người viết.

Một ví dụ về Dockerfile để build ubuntu-wget image ở trên như sau:

FROM ubuntu:14.04
MAINTAINER lam.dv
RUN apt update
RUN apt install wget -y

Format cơ bản của Dockerfile theo kiểu:

# Comment
INSTRUCTION arguments

Đọc kĩ hơn về Dockerfile ở trang document của docker: https://docs.docker.com/engine/reference/builder/

=======================================================

Cơ bản giới thiệu xong rồi, bây giờ đến Layer ………………………………

Một docker image được xây dựng từ các layer, mỗi layer được tạo ra từ 1 lệnh trong Docker file. Lệnh nào chạy sau sẽ tạo 1 layer nằm trên layer được tạo từ lệnh chạy trước nó, cứ thế cho đến khi hết Dockerfile. Mỗi layer này là chỉ đọc (Read Only), không thể ghi vào các layer này. Khi docker chạy container từ docker image đã tạo, docker sẽ tạo ra 1 layer R/W (Read/Write) nằm trên tất cả các layer khác của image, khi ta thực hiện cài thêm app, thêm file, xoá file, balabala, …. tất cả sẽ diễn ra trên layer này, và nó thường được gọi là container layer.

Source: https://docs.docker.com/storage/storagedriver/#container-and-layers

Ví dụ như Dockerfile ở trên, khi ta build nó docker build -t ubuntu-wget:fromDockerfile ., nó sẽ tạo ra các layer cùng với id như bên dưới, ví dụ Step 1/4 có id là 3b853789146f

Ta chạy docker history ubuntu-wget:fromDockerfile, sẽ có được danh sách các layer của image này.

Đó là về layer của image, giờ mình thêm một chút về layer của container. Điểm khác biệt của container với image nằm ở cái container R/W ở trên cùng các layer R/O của image khi container được chạy. Nhờ nó mà ta có thể thực hiện các thao tác thêm, sửa, xoá, … về dữ liệu lên container như một cái OS bình thường. Khi container bị xoá, layer này cũng bị xoá theo, các layer R/O khác của image thì còn nguyên. Nếu ta commit thì layer này sẽ trở thành một layer R/O khác nằm trên các layer cũ đã có của image.

Mỗi container có một layer R/W riêng nằm trên các layer R/O của image, ta có thể chạy nhiều container từ một image, các container dùng chung với nhau phần image nằm dưới container layer.

 

Source: https://docs.docker.com/storage/storagedriver/#container-and-layers

Ví dụ ta có thể chạy cùng lúc 3 container từ image ubuntu-wget:fromDockerfile đã tạo ở trên:

Như vậy, chốt lại một cách đơn giản là image gồm nhiều layer và mỗi layer được tạo ra từ một lệnh trong docker file hoặc một lần commit. Như vậy, ta sẽ có 1 cách để tối ưu cái kích thước của docker image khi ta build nó, đó là giảm bớt số lệnh trong docker file bằng cách gộp nhiều lệnh lại thành một lệnh (Sử dụng Dockerfile nhé, còn vụ chạy container lên quẩy rồi commit thì bỏ đi).

Ví dụ như ở Dockerfile trên, thay mình mình chạy 2 lệnh run thế này

RUN apt update
RUN apt install wget -y

Thì mình gộp lại thành 

RUN apt update && apt install wget -y

khi gộp lệnh thế này thì hãy đảm bảo là mỗi lệnh con sẽ chạy thành công, ví dụ apt update phải chạy thành công, nếu không thì apt install wget -y sẽ k chạy, nhìn cái dấu && kìa.

Cơ bản hiểu layer là vậy thôi, mình bàn thêm xíu về kỹ thuật Union File System mà docker sử dụng với mấy cái layer này:

Docker sử dụng Union File System (UFS), bạn có thể xem chi tiết về nó ở đây: https://en.wikipedia.org/wiki/UnionFS, cơ bản trong docker image thì UFS sẽ gộp file và thư mục trên các layer lại, hiển thị ra bên ngoài không phải là từng layer mà là một thể thống nhất.

Ví dụ như ta có 3 image layer và container layer như trên, container sẽ thấy hết các file và folder nằm ở các layer khác nhau, nếu 2 layer đều có 1 file với đường dẫn i chang nhau thì nó sẽ thấy file nằm ở layer trên, như hình trên thì thấy file2 ở image layer 2. Khi container nó thực hiện thay đổi trên 1 file nào đó, thì file đó ở dưới image layer bị che đi, bây giờ nó thấy file đó trên container layer, ví dụ như ở trên, ta thay đổi file3, sau đó ta cần dùng file3 1 lần nữa thì container nó lấy file3 ở trên container layer mình thay đổi lúc nãy chứ không phải file3 ở Image base layer. Các layer này như là transparent, để từ trên có thể nhìn thông suốt xuống dưới.

Có nhiều những file system để hiện thực Union File System, ví dụ như aufs, overlay, overlay2, devicemapper, btrfs….ví dụ như trên máy mình thì Docker nó dùng overlay2

Liên quan đến vấn đề này còn nhiều thứ chuyên sâu hơn như là Copy-on-write trategy, Docker Storage Drivers, … mình nghĩ tới đây là đủ cho tối nay rồi, bạn có thể tìm hiểu ở ……………. somewhere on the internet :p

Tham khảo: https://docs.docker.com/storage/storagedriver/

Leave a Reply

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