[Learn Docker for DevOps Engineers P1] Kiến trúc Docker và Dockerfile.

Nội dung

  • Mở đầu
  • Đối tượng đọc bài viết này
  • So sánh Docker và Virtual Machine
  • Kiến trúc và các khái niệm cơ bản trong Docker
  • Dockerfile Instructions (P1 đến đây)
  • Lightweight Your Docker Images.
  • Kết luận

Mở đầu

Có lẽ khái niệm docker đã không còn quá xa lạ với dân IT chúng ta nữa, trong bài viết này mình sẽ trình bày docker không phải ở khía cạnh "HOW TO USE" mà là những kiến thức có thể nếu bạn chỉ muốn biết dùng docker thế nào, buil image phục vụ công việc ra sao thì cũng chỉ cần đọc qua là được, nhưng với Devops thì cần hiễu cặn kẽ về nó, nó sẽ cung cấp nhiều kiến thức cho việc vận hành hệ thống lớn sử dụng hàng chục, hàng trăm container.
Trong quá trình vận hành hệ thống Microservice sử dụng CI/CD cùng với việc áp dụng công nghệ Kubernetes vào dự án, mình thường xuyên tiếp xúc với docker container. Vì thế, mình sẽ tổng hợp một số kiến thức dưới góc nhìn của Devops.

Đối tượng đọc bài viết này

Với các bạn mới bắt đầu tìm hiểu về docker thì bài viết này vừa thừa lại vừa thiếu, thừa những kiến thức các bạn chưa cần đến, vì có thể bạn chỉ cần kéo 1 image về chạy thôi. Những bạn cần biết cách dùng ngay thì bài viết này chưa đủ vì nó không đề cập nhiều đến cụ thể các command, cách pull, run..vv như thế nào.
Vậy đối tượng nào sẽ phù hợp cho bài viết này?
Bài viết này theo mình là phù hợp với những bạn đã biết sơ sơ hoặc đã có chút kinh nghiệm về docker rồi nhưng chưa hiểu hết bản chất của nó, hoặc là bạn chưa hề suy nghĩ đến việc làm sao để lightweight container của mình.

So sánh Docker và Virtual Machine

Vitural Machines (VMs) về bản chất là một giả lập của một máy tính để thực thi các ứng dụng giống như một máy tính thật. VMs chạy trên một máy vật lý sử dụng một thứ gọi là “hypervisor”. Hypervisor có thể là phần cứng, phần mềm hoặc là một bản firmware nào đó có thể chạy trực tiếp trên máy thật (host machine) có chức năng cho nhiều máy ảo chạy trên nó. Host machine sẽ cung cấp cho VMs những tài nguyên như là RAM, CPU. Những tài nguyên đó sẽ được phân bổ giữa các VMs theo cách mà bạn cho là hợp lý. Nếu một VM chạy nhiều ứng dụng hoặc nặng thì bạn phải cung cấp tài nguyên cho nó nhiều tài nguyên hơn những VMs khác trên cùng một host machine.

Những VMs chạy trên host machine thường được gọi là guest machine. Guest machine này sẽ chứa tất cả những thứ mà hệ thống cần để chạy ứng dụng như hệ điều hành (OS), system binaries và libraries. VMs chạy trên hệ điều hành của host machine và không thể truy cập trực tiếp đến phần cứng mà phải thông qua hệ điều hành.

Container không giống như VMs, Container không cung cấp sự ảo hóa về phần cứng. Một Container cung cấp ảo hóa ở cấp hệ điều hành bằng một khái niệm trừu tượng là “user space”. Sự khác nhau lớn nhất của Container và VMs là Container có thể chia sẻ host system’s kernel với các container khác. Cùng xem mô hình bên dưới để hiểu rõ hơn về Container.

Nguồn: https://techglimpse.com/docker-installation-tutorial-centos/

Dựa vào sơ đồ bên trên. Các bạn có thể thấy các gói container chỉ là một user space bao gồm ứng dụng, system binaries và libraries mà không cần guest OS hoặc ảo hóa phần cứng như VMs. Đây là cái mà làm cho các container nhẹ hơn (lightweight). Các container sẽ chạy trên công nghệ cụ thể ở đây là Docker Engine.

Kiến trúc và các khái niệm cơ bản trong Docker.

Để dễ hình dung thì mình chia ra thành Khái niệm cơ bản và mở rộng:

Khái niệm cơ bản

Docker Platform:

Docker Platform là phầm mềm docker cung cấp khả năng đóng gói và chạy một ứng dụng trong một môi trường tách biệt lỏng lẻo gọi là container trên bất kỳ máy chủ Linux nào. Docker Platform gói các code files và các thành phần phụ thuộc. Nó dễ dàng mở rộng với khả năng tái tạo và tính di động.

Docker Engine

Docker Engine là một ứng dụng client-server. Công ty Docker chia Docker Engine thành 2 sản phẩm. Docker Community Edition (CE) miễn phí dựa trên các công cụ mã nguồn mở, đó có lẽ là những gì bạn sẽ sử dụng. Docker Enterprise đi kèm với những tính năng bổ sung như: support, quản lý, bảo mật.

Docker Client

Docker Client là cách chính để bạn tương tác với docker. Khi bạn sử dụng Docker Command Line Interface (CLI) và đánh command vào terminal, nó sẽ bắt đầu bằng "docker". Docker Client sau đó sẽ sử dụng Docker API để gửi command đến Docker Daemon.

Nguồn: https://docs.docker.com/engine/docker-overview/#docker-architecture

Docker Daemon

Docker Daemon là máy chủ Docker lắng nghe yêu cầu API Docker. Docker Daemon quản lý image, container, network và volumes.

Docker Volumes

Docker volumes là cách tốt nhất lưu trữ dữ liệu liên tục mà application sử dụng và tạo ra.

Docker Registry

Docker Registry là một khu vực lưu trữ từ xa, nơi mà Docker Images được lưu trữ. Chúng ta thường pull/push docker image từ Docker Registry. Chúng ta có thể tự xây dựng Docker Registry cho riêng mình hoặc sử dụng Registry của nhà cung cấp như AWS và Google Cloud.

Docker Hub

Docker Hub là một registry lớn nhất của Docker Images, nó cũng là registry default. Bạn có thể tìm kiếm và lưu trữ Docker Images của bạn trên Docker Hub miễn phí.

Docker Repository

Docker Repository là tập hợp những docker images có cùng tên nhưng khác nhau tags. Tags là định danh cho docker images. Thông thường thì một repostory sẽ có nhiều version khác nhau cho cùng 1 images. Ví dụ như Python là một image rất phổ biến của
official Docker trên Docker Hub. Python:3.7-slim dùng để chỉ phiên bản của images với tag 3.7-slim trong Python repository. Bạn có thể push một repository hoặc 1 images đơn đến registry.

Khái niệm mở rộng

Những khái niệm dưới đây liên quan đến sử dụng nhiều container trên 1 máy.

Docker Network

Docker Network cho phép bạn kết nối nhiều container lại với nhau, các container có thể nằm trên cùng 1 host hoặc nhiều host khác nhau. Các bạn có thể tìm hiểu chi tiết tại đây.

nguồn: https://docs.docker.com/engine/tutorials/networkingcontainers/

Docker Compose

Docker Compose là một công cụ giúp bạn dễ dàng hơn trong việc chạy ứng dụng trên nhiều container. Docker Compose cho phép bạn di chuyển các command vào bên trong file docker-compose.yml cho việc sử dụng lại. Docker Compose command line interface (CLI) giúp chúng ta dễ tương tác hơn với ứng nhiều nhiều container. Docker Compose là miễn phí.

Docker Swarm

Docker Swarm được gọi là orchestrate container. Các bạn có thể tham khảo thêm ở phần tài liệu chính thức của Docker Docker tutorial

Kubernetes

Kubernetes tự động hóa việc triển khai, Scaling, và quản lý các ứng dụng chạy bằng container. K8s áp đảo hoàn toàn trong số các công cụ container orchestration. Thay vì sử dụng Docker Swarm, hãy sử dụng K8s để scale up project của bạn với hàng chục, hàng trăm container.

Bản thân mình đã có hơn 4 năm kinh nghiệm phát triển web và hơn 2 năm kinh nghiệm về vận hành hệ thống thì mình đánh giá K8s rất tuyệt vời. Mình đã được tiếp xúc với k8s trong khoảng 1 năm trở lại đây khi project của mình quyết định container hóa ứng dụng. Quả thật còn rất nhiều điều thú vị nữa mà K8s cung cấp, mới chỉ sử dụng khoảng 50% thôi đã thấy quá tuyệt vời rồi. :D

Dockerfile Instructions

Dockerfile là file định nghĩa nội dung của một Docker Images, Lệnh trong Dockerfile được viết hoa theo sau là các đối số của nó. Mỗi dòng trong Dockerfile có thể chứa 1 lệnh. Các lệnh trong Dockerfile được xử lý từ trên xuống dưới khi images được build. Một Dockerfile đơn giản trông như dưới đây:

FROM ubuntu:18.04
COPY . /app

Chỉ các lệnh như FROM, RUN, COPY và ADD sẽ tạo ra các layer khi build image. Các lệnh khác dùng để cấu hình mọi thứ, add metadata, hoặc nói với Docker làm một số việc gì đó trong quá trình runtime như là expose port hoặc run command.

Tóm tắt các lệnh trong Dockerfile

FROM — Chỉ định images gốc (parent image).
LABEL — cung cấp metadata. Nơi để viết thông tin maintainer.
ENV — thiết lập biến môi trường.
RUN — Chạy 1 command để tạo 1 images layer. Được sử dụng để cài đặt các gói vào container.
COPY — copy files và thư mục vào trong container.
ADD — copy files và thư mục vào trong container. Bao gồm việc giải nén các tập tin .tar
CMD — Cung cấp command và arguments cho việc thực thi container. Parameters có thể được ghi đè.
WORKDIR — Thiết lập thư mục làm việc, nơi thực hiện các lệnh trong Dockerfile.
ARG — định nghĩa một biến để truyền đến Docker trong khi build-time.
ENTRYPOINT — cung cấp lệnh và đối số cho một container thực thi.
EXPOSE — exposes một port.
VOLUME — tạo thư mục để truy cập và lưu trữ dữ liệu liên tục. Thư mục trên máy host sẽ đồng bộ với thư mục trong container, dữ liệu sẽ không bị mất khi khởi động lại container.

FROM

Chúng ta có 1 Dockerfile đơn giản như sau:

FROM ubuntu:18.04

Dockerfile phải bắt đầu bằng lệnh FROM hoặc lệnh ARG theo sau là lệnh FROM. Lệnh FROM sẽ nói với Docker là nó sẽ sử dụng một base images được cung cấp bởi repository và tag. Base images còn được gọi là parent images.

Trong ví dụ này thì Ubuntu là một images repository, Ubuntu là một tên của official Docker repository được cung cấp phiên bản cơ bản của trong các phiên bản phổ biến của Linux operating system.
Lưu ý rằng file Dockerfile trong ví dụ của chúng ta bao gồm phiên bản 18.04, tag này sẽ cho Docker biết chúng ta sẽ pull phiên bản nào của Ubuntu, nếu không gắn tag, mặc định sẽ pull phiên bản latest (default).
Khi Dockerfile phí trên được sử dụng để build images lần đầu tiên, Docker sẽ download các layer chỉ định trong ubuntu images. Các layer có thể được xếp chồng lên nhau, mỗi layer là một tệp khác với các layer trước đó.
Khi run 1 container, chúng ta sẽ add một writable layer lên phía trên read-only layers.

Nguồn: https://docs.docker.com/v17.09/engine/userguide/storagedriver/imagesandcontainers/#images-and-layers

Cùng tìm hiểu một images phức tạp hơn:

FROM python:3.7.2-alpine3.8
LABEL maintainer="jeffmshale@gmail.com"
ENV ADMIN="jeff"
RUN apk update && apk upgrade && apk add bash
COPY . ./app
ADD https://raw.githubusercontent.com/discdiver/pachy-vid/master/sample_vids/vid1.mp4 \
/my_app_directory
RUN ["mkdir", "/a_directory"]
CMD ["python", "./my_script.py"]

Base image là images của official Python với tag 3.7.2-alpine3.8. Image Alpine thường rất phổ biến vì chúng nhỏ, nhanh và an toàn. Tuy nhiên cũng vì thế mà Alpine không bao gồm nhiều gói phầm mềm trong đó, chúng ta phải tự cài đặt nhưng gói cần thiết để sử dụng.

LABEL

Lệnh LABEL thêm metadata vào image, trong trường hợp phía trên, nó cung cấp thông tin của maintainer image. Labels không làm chậm hoặc chiếm không gian trong lúc build images và nó cung cấp thông tin hữu ích về Docker images, vì vậy chúng ta nên sử dụng nó. Thông tin thêm tại đây.

ENV

ENV thiết lập biến môi trường, biến này sẽ được sử dụng trong quá trình container run time. Trong ví dụ trên, chúng ta có thể sử dụng biến ADMIN khi container được khởi chạy.
ENV thường dùng để setting cho hằng số. Nếu bạn sử dụng constants nhiều vị trí trong Dockerfile và bạn muốn thay đổi giá trị của nó sau đó, bạn có thể thực hiện việc đó trong 1 vị trí cụ thể.

Với Dockerfiles thường có nhiều cách để thực hiện cùng một việc. Phương pháp tốt nhất cho trường hợp của bạn là vấn đề cân bằng các conventions, tính minh bạch và tốc độ của Docker. Ví dụ, RUN, CMD và ENTRYPOINT phục vụ các mục đích khác nhau và tất cả đều có thể được sử dụng để thực thi các command.

RUN

Lệnh RUN sẽ tạo ra một layer trong quá trình build-time, Docker sẽ commits trạng thái của mối image sau mỗi lệnh RUN.
RUN thường được dùng để cài đặt các gói vào images, trong ví dụ phía trên RUN apk update && apk upgrade nói với Docker để update các package từ base images. và && apk add bash nói với Docker là cài đặt bash vào trong images.
apk viết tắt của Alpine Linux package manager, nếu bạn đang sử dụng một base image khác của Linux khác với Alpine, thì chúng ta sẽ sử dụng RUN apt-get thay vì RUN apk.
RUN - người anh em của nó là CMD và ENTRYPOINT - có thể được sử dụng ở dạng exec hoặc shell. Dạng exec sử dụng JSON aray có syntax như: RUN ["my_executable", "my_first_param1", "my_second_param2"].
Ví dụng trên chúng ta đã sử dụng dạng shell với format là: RUN apk update && apk upgrade && apk add bash, và cũng đã sử dụng định dạng exec: RUN ["mkdir", "/a_directory"] để tạo thư mục, bạn đừng quên dấu ngoặc kép nhé !

COPY

Lệnh COPY . ./app bên trên nói với Docker rằng lấy tất cả files và thư mục ở thư mục hiện tại (cùng cấp với Dockerfile) và thêm chúng vào thư mục làm việc hiện tại của Docker Images (app). Copy sẽ tạo đường dẫn directory nếu chúng chưa tồn tại.

ADD

ADD hoạt động giống với COPY nhưng có 2 trường hợp sử dụng, ADD có thể sử dụng để move files từ remove URL đến container và ADD có thể giải nén các tập tin TAR.
Phía trên mình đã sử dụng ADD để coppy file từ URL vào thư mục my_app_directory trong container, Tài liệu Docker không khuyến khích việc này vì các tập tin ở remote URL có thể bị xóa, Nhưng file mở rộng sẽ làm tăng kích thước của images.
Tài liệu Docker cũng khuyến khích sử dụng COPY thay vì ADD bất cứ khi nào có thể để cải thiện sự rõ ràng. Tại sao Docker không kết hợp COPY và ADD là một cho khỏe nhỉ? :D
Lưu ý lệnh ADD chưa ký tự tiếp tục "\". Sử dụng nó để Dockerfile trông rõ ràng, dể đọc hơn bằng cách chia thành nhiều dòng.

CMD

CMD cung cấp cho Docker một command để run khi container được khởi động. Nó không commit kết quả của command cho image tại thời điểm build time. Trong ví dụ trên CMD sẽ thực hiện chạy file my_script.py tại thời điểm runtime.
Một vài điều cần biết về CMD:

  • Chỉ có duy nhất một lệnh CMD cho mỗi Dockerfile.
  • CMD có thể bao một một lệnh executable. Nếu CMD tồn tại mà không có executable thì phải tồn tại ENTRYPOINT. Trong trường hợp đó, cả CMD và ENTRYPOINT phải có định dạng JSON.
  • Các đối số để docker run override được cung cấp cho CMD trong Dockerfile.

Chúng ta cùng xem xét một ví dụ về Dockerfile khác

FROM python:3.7.2-alpine3.8
LABEL maintainer="jeffmshale@gmail.com"
# Install dependencies
RUN apk add --update git

# Set current working directory
WORKDIR /usr/src/my_app_directory

# Copy code from your local context to the image working directory
COPY . .

# Set default value for a variable
ARG my_var=my_default_value

# Set code to run at container run time
ENTRYPOINT ["python", "./app/my_script.py", "my_var"]

# Expose our port to the world
EXPOSE 8000

# Create a volume for data storage
VOLUME /my_volume

WORKDIR

WORKDIR sẽ thay đổi working directory trong container cho các lệnh COPY, ADD, RUN, CMD, và ENTRYPOINT theo sau nó. Một vài lưu ý:

  • Nên chỉ định đưỡng dẫn tuyệt đối thay vì điều hướng bằng các lệnh hệ thống như 'cd' trong Dockerfile.
  • WORKDIR tự động tạo directory nếu nó không tồn tại
  • Có thể sử dụng nhiều lệnh WORKDIR. Nếu chúng ta cung cấp các đường dẫn tương đối, thì mỗi lệnh WORKDIR sẽ thay đổi thư mục làm việc hiện tại.

ARG

ARG định nghĩa một biến để truyền từ command line đến image tại thời điểm build time. Một giá trị mặc định có thể được cung cấp cho ARG trong Dockerfile giống như trong ví dụ trên: ARG my_var=my_default_value
Không giống như biến ENV, biến ARG không available để chạy container. Tuy nhiên, bạn có thể thiết lập giá trị mặc định cho một biến ENV từ giao diện command line khi bạn build image, Sau đó, biến ENV sẽ tồn tại trong suốt thời gian container run. Tìm hiểu thêm tại đây.

ENTRYPOINT

ENTRYPOINT cũng cho phép bạn cung cấp một command và một đối số mặc định khi container start. Nó trông có vẻ như CMD, nhưng các tham số ENTRYPOINT không bị ghi đè nếu một container chạy với tham số ở command line.
Thay vào đó, các tham số dòng lệnh được truyền cho docker chạy my_image_name sẽ được gắn vào đối số của ENTRYPOINT. Ví dụ 'docker run my_image bash' thêm tham số bash vào cuối các tham số của ENTRYPOINT.
Một Dockerfile nên có ít nhất 1 CMD hoặc ENTRYPOINT.
Tài liệu Docker có một số gợi ý để chọn CMD hoặc ENTRYPOINT cho lệnh init container.

  • Ưu tiên ENTRYPOINT khi bạn cần chạy cùng một lệnh mỗi lần.
  • Ưu tiên ENTRYPOINT khi một container sẽ được sử dụng như một chương trình thực thi.
  • Ưu tiên CMD khi bạn cần cung cấp thêm các đối số mặc định có thể được ghi đè từ dòng lệnh.
    Trong ví dụ trên, ENTRYPOINT ["python", "my_script.py", "my_var"] sẽ chạy một python script my_script.py với tham số là my_var với my_var là tham số khi container bắt đấu chạy. my_var có thể được sử dụng bởi my_script thông qua argparse. Lưu ý my_var có một giá trị default được cung cấp bởi lệnh ARG trong Dockerfile. Vì vậy nến chung ta không gửi tham số từ giao diện command line, thì giá trị default được sử dụng.
    Docker khuyên bạn nên sử dụng định dạng exec form của ENTRYPOINT: ENTRYPOINT ["executable", "param1", "param2"] - đây là định dạng với format JSON.

EXPOSE

Lệnh EXPOSE sẽ chỉ ra cổng nào được publish ra ngoài để cung cấp việc truy cập đến container.

EXPOSE does not actually publish the port. Rather, it acts as a documentation between the person who builds the image and the person who runs the container.

Sử dụng docker run với flag -p để publish và map một hoặc nhiều ports tại thời điểm runtime. flag -P sẽ publish tất cả các exposed ports.

VOLUME

VOLUME sẽ chỉ định nơi bạn sẽ lưu trữ và/hoặc truy cập đến dữ liệu liên tục (persistent data). Liên quan đến Docker Volume, các bạn có thể tham khảo tại đây!

Tổng kết

Như vậy trong phần 1 này mình đã cơ bản tổng kết lại các kiến thức cơ bản về Docker. Hơi nhiều lý thuyết, tuy nhiên các bạn đọc và kiểm chứng vì mình nghĩ đọc bài này đa số các bạn cũng từng làm việc hoặc chạm đến nó rồi. Phần 2 sẽ có nhiều demo hơn.

  • So sánh với Hypervisor
  • Các khái niệm cơ bản trong Docker
  • Các lệnh trong Dockerfile

Phần 2 mình sẽ trình bày làm thế nào để xây dựng docker image hiệu quả nhất:

  • Dung lượng nhẹ
  • Build nhanh
  • Giảm bớt số lượng layer
  • Khả năng tái sử dụng Layer
  • vv.
    Mong các bạn đọc và đóng góp ý kiến!