Note nhanh và cơ bản về Docker

Docker

Rất nhanh về Docker

  • Docker để dễ hiểu mình sẽ nói sai định nghĩa của nó một chút. Hiểu docker như một máy ảo, nhưng bạn có thể khởi tạo hay quản lý “máy ảo” này hoàn toàn bằng lệnh thông thường. Thế nên bạn có thể rất dễ quản lý và khởi tạo những cái “máy ảo” thông qua vài dòng lệnh.
  • Bạn sẽ có thể viết những config để Docker xây dựng “máy ảo” và chia sẻ file config cho người khác để build một “máy ảo” giống 100% của bạn. Mà không cần những file snapshot bự tới hàng Gigabytes
  • Tuy nhiên cái hay hơn của Docker so với máy ảo, là Docker chỉ áo hoá phần hệ điều hành. Vì thế Docker sẽ ít tốn tài nguyên hơn và ít tốn dung lượng của ổ cứng hơn. Để dễ hình dung, Docker sẽ giúp bạn chạy 1 hệ điều hành y như một phần mềm trên máy của bạn. Không cần VMWare hay bất kỳ hypervisor nào khác
  • Hầu hết mọi người dùng Docker để tạo ra một môi trường chuẩn để phát triển phần mềm cho nhóm. Tránh việc test trên máy A bị lỗi nhưng máy B thì lại không bị gì do khác nhau về thư viện, hệ điều hành, cài đặt và nhiều thứ khác

Thành phần siêu cơ bản của Docker

  • Image ~ Class trong lập trình. Là file “máy ảo” đã được Docker xây dựng với những config bạn tuỳ chỉnh.
  • Container ~ Object/Instance trong lập trình. Ở Docker nó có nghĩa là instance của Image được build ra. Khi bạn bật “máy ảo” từ image, thì cái máy ảo đang chạy đó gọi là container

Image

  • Pull image từ docker hub về máy
    docker pull <image_name>:<tag> # image_id cũng ok
    
  • Chạy một image (tạo container)
    docker run <image_name>:<tag> # image_id cũng ok
    
  • Tạo container với tên cho container đó
    docker run --name=<container_name> <image_name>:<tag> # image_id cũng ok
    
    Ví dụ
    docker run --name=my-container example_python:1.0
    
  • Xem các image hiện có
    docker images
    
    hoặc
    docker image ls
    
  • Khi chạy một docker, tức ta sẽ tạo ra 1 container
  • Image id, Image tag cso thể tìm thấy ở trong các lệnh xem images
  • Một image có thể tạo ra nhiều container

Container

  • Docker container có dung lượng cực kỳ nhẹ, nó chỉ tính thêm dung lượng được tạo ra bởi các file trong container đó
  • Để xem các container đang chạy
    docker ps
    
    hoặc
    docker container ls
    
  • Để xem toàn bộ container đã tắt và chạy
    docker container ls -a
    
  • Nếu bạn không muốn chạy một container nào đó nữa
    docker stop <container_id>
    
    hoặc
    docker stop <container_name>
    
  • Container name và Container ID có thể tìm thấy trong lệnh xem container đang chạy hoặc lệnh xem toàn bộ container
  • Ép một container phải dừng ngay lập tức
    docker kill <contaier_id|contaier_name>
    
  • Nếu bạn muốn restart một container đang chạy
    docker restart <container_id|container_name>
    
  • Bạn muốn chạy lại một container đã tắt
    docker start <container_id|container_name>
    

Execute Command

  • Với Dockerfile, có lệnh CMD ở cuối cùng. Có thể override command đó với lệnh sau

    docker run <image_name>:<tag> <new_command> # image_id cũng ok
    

    Ví dụ

    docker run example_python:1.0 python -u software2.py
    
  • Khi này, nếu có dòng

    CMD ["python", "-u", "software.py"]
    

    Thì nó sẽ bị thay thế bởi python -u software2.py

  • Nếu Dockerfile, không có bất kỳ ENTRYPOINT hoặc CMD nào. vẫn có thể chạy container đó với command bất kỳ bằng với lệnh sau

    docker run <image_name>:<tag> <command to run> # image_id cũng ok
    

    Ví dụ

    docker run example_python:1.0 python -u abc.py
    
  • Nếu bạn muốn đi vào trong docker để debug hoặc chỉnh sửa file, dùng lệnh

    docker exec -it <container_id|container_name> /bin/bash
    
  • Lưu ý phải có option -it nếu muốn tương tác với bash shell. Nếu không, docker chỉ đơn giản chạy lệnh và hiển thị logs ra màn hình

  • Nếu chỉ muốn chạy lệnh đơn giản, như chạy 1 file python, thì không cần -it cũng được

    docker exec <container_id|container_name> python abc.py
    

Logs

  • Để xem logs của docker container đang chạy
    docker logs <container_id|container_name>
    
  • Lệnh trên sẽ show ra TOÀN BỘ LOGS. Nếu muốn giới hạn logs lại khoảng 5’ gần đây, hoặc 5s gần đây thì thêm option --since
    docker logs <container_id|container_name> --since=5m
    
    docker logs <container_id|container_name> --since=5s
    
  • Nếu muốn follow các logs mới, xài option -f
    docker logs <container_id|container_name> --since=5s -f
    
    docker logs <container_id|container_name> -f
    

Image Build

  • Nếu có sẵn file Dockerfile ở folder hiện tại. Xài lệnh build để build Dockerfile ra Docker Image
    docker build .
    
  • Nếu có sẵn file Dockerfile ở folder khác.
    docker build ./path/to/Dockerfile/folder
    
  • Mặc định docker image có thể không có tên + tag. Để thêm tên + tag, thêm option -t <name>:<tag>
    docker build . -t <name>:<tag>
    
  • Nếu không có file Dockerfile mà một file khác như abc.dockerfile. Xài option -f để chỉ định file name
    docker build -f <filename> <path> -t <name>:<tag>
    
    Ví dụ
    docker build -f python.dockerfile ./my-docker -t my-python:1.0
    

Image Push

  • Tạo sẵn 1 tài khoảng trên hub.docker.com
  • Khi tạo docker name, tạo theo định dạng
    <docker_username>/<image_name>
    
  • Nếu lỡ tạo 1 tag khác định dạng trên, tag lại bằng lệnh
    docker tag <name>:<tag> <docker_username>/<image_name>:<tag>
    
    Ví dụ
    docker tag example:latest foo/example:latest
    
  • Login vào docker hub
    docker login
    
  • Làm theo chỉ dẫn login của docker login tới khi dòng chữ Login Successfully
  • Push lên docker hub
    docker push <docker_username>/<image_name>:<tag>
    
  • Nếu không có tag, mặc định nó sẽ là latest

Port Expose

  • Mặc định docker sẽ tạo ra 1 môi trường network không thể truy cập từ máy host và host cũng không thể truy cập container. Đây là chính sách bảo mật của docker vì có thể có người lừa người dùng chạy các docker image nguy hiểm
  • Để public 1 port từ docker ra ngoài, ta xài option -p
    docker run -p <host_port>:<container_port> <image>
    
    Ví dụ
    docker run -p 1234:8000 <image>
    
    Lệnh trên sẽ map port 8000 (tại địa chỉ 0.0.0.0) của container ra port 1234 của địa chỉ 0.0.0.0. Tức khi này, khi ta truy cập 0.0.0.0:1234 (host) tức ta sẽ truy cập được 0.0.0.0:8000 ở container
  • Lưu ý, nếu container chạy trên địa chỉ 127.0.0.1 = localhost, thì port sẽ không được public ra.
  • Mặc định port đc expose sẽ ra trên địa chỉ 0.0.0.0, để tránh máy khác trong cùng subnet truy cập. Ta thêm 127.0.0.1 vào host_port
    docker run -p 127.0.0.1:<host_port>:<container_port> <image>
    
  • Mặc định port là TCP, nếu muốn expose UDP xài lệnh
    docker run -p <host_port>:<container_port>/UDP <image>
    
  • Ta cũng có thể expose 1 range của port
    docker run -p from-to:from-to <image>
    
  • Ví dụ
    docker run -p 7000-8000:7000-8000 <image>
    
    Khi này ta sẽ expose 1k port từ port 7000 -> port 8000
  • Cú pháp ngắn khi port host và port container giống nhau
    docker run -p <port>:<port> <image>
    
    Sẽ rút gọn thành
    docker run -p <port> <image>
    
    Tương đương
    docker run -p 127.0.0.1:<host_port>:<container_port> <image>
    
  • Ví dụ về cú pháp ngắn
    docker run -p 8000 <image>
    
    Khi này truy cập vào localhost:8000 sẽ truy cập vào được 0.0.0.0:8000 ở container

Volume Mounting

  • Khi 1 file được copy vô container, file và file bên ngoài host là 2 file tồn tại độc lập. Tức file A ở ngoài host có chỉnh sửa, thì file A trong container cũng sẽ không ảnh hưởng
  • Điều này sẽ làm khó trong việc lưu trữ file từ container ra ngoài host hay việc chỉnh sửa code từ host để debug trên container
  • Để container và host trỏ tới cùng 1 file / folder, ta sẽ xài option -v (volume)
    docker run -v host/path:container/path <image>
    
    Ví dụ
    docker run -v D:\Docker\fileA.md:/app/fileA.md <image>
    
  • Ví dụ trên thì khi này host và docker sẽ cùng đọc chung 1 file là fileA
  • Tức nếu host có đổi fileA thì fileA trong container cũng sẽ thay đổi theo
  • Tương tự, nếu container thay đỔi fileA thì host cũng sẽ thấy nó được thay đổi
  • Ví dụ folder
    docker run -v D:\Docker:/home/foo <image>
    
  • Khi này Dockerfoo dù khác tên nhưng đều trỏ về 1 folder là Docker. Mọi sự thay đổi của Docker đề sẽ ảnh hưởng lên foo và ngược lại

Environment Variables

  • Xài lệnh -e hoặc --env để thêm các biến môi trường.
  • Một biến môi trường phổ biến trong linux chính là PATH
    docker run -e MY_NAME=foo -e MY_FEN=bar <image>
    
  • Doạn trên có thể viết thành
    docker run -e MY_NAME=foo,MY_FEN=bar <image>
    
  • Hoặc có thể dùng file. Với cú pháp KEY=VALUE
  • Ví dụ file my-env.env
    MY_NAME=foo
    MY_FEN=bar
    
    docker run --env-file=my-env.env <image>
    
  • Cả 3 ví dụ trên, khi execute vào container và chạy lệnh
    echo $MY_NAME
    echo $MY_FEN
    
  • Sẽ ra kết quả
    foo
    bar
    

Dockerfile

  • Một Dockerfile luôn và phải luôn có lệnh FROM
    FROM <image_name>:<tag>
    
  • Ví dụ
    FROM python:3.9-alpine
    
    Sẽ lấy base image của python, version 3.9 về
  • FROM ubuntu:20.04
    
    Sẽ lấy base image là ubuntu version 20.04
  • Nếu không có image ưng ý và muốn build từ đầu
    FROM scratch
    

  • Để chạy các lệnh setup Dockerimage, ta xài lệnh RUN
  • Lưu ý, khi xài RUN, khi build 1 docker image. Docker sẽ chờ cho lệnh RUN này chạy xong. -> TUYỆT ĐỐI KHÔNG DÙNG LỆNH RUN ĐỂ CHẠY CHƯƠNG TRÌNH CHÍNH
    RUN <command>
    
  • Ví dụ
    RUN apt update && apt install -y
    
    Để update và cài dặt toàn bộ package của ubuntu lên phiên bản mới nhất
  • Một lưu ý nữa, các command trong RUN, tuyệt đối không được đòi hỏi người dùng phải tương tác với nó. Như nhấn yes để tiếp tục, điền thông tin, v.v
  • RUN python inf_loop
    
  • Ở ví dụ trên, khi build, docker sẽ chờ lệnh này vĩnh viễn

  • Tạo ra một folder để làm việc. Ta xài lệnh WORKDIR
  • WORKDIR /path/to/work/folder
    
  • Nếu folder chưa có, nó sẽ tự động tạo, và chuyển thư mục hiện hành tới folder vừa tạo
  • WORKDIR /app
    
  • Lệnh trên sẽ tạo ra folder /app và chuyển thư mục đang ở hiện tại (ví dụ như /home) sang /app
  • WORKDIR là bao gồm sự kết hợp của 2 lệnh
  • RUN mkdir /path/to/work/folder
    RUN cd /path/to/work/folder
    

  • Để copy các file hiện hành sang container, ta xài lệnh COPY
  • COPY src dest
    
  • Ví dụ
    COPY . .
    
    Copy toàn bộ nội dung của thư mục đang đứng, sang thư mục hiện tại trong container
  • COPY requirement.txt .
    
    Copy file requirement.txt sang folder hiện tại trong container
  • COPY ./xyz/abc.py /app/abc.py
    
    Copy file abc.py từ folder xyz của host sang folder /app của container

  • Để kích hoạt một lệnh khi 1 container được khởi tạo, ta xài CMD hoặc ENTRYPOINT
  • Với lệnh CMD, ta có thể replace lệnh khi tạo container như ở ví dụ container trên
  • Còn với ENTRYPOINT, ta không thể replace lệnh này khi khởi tạo container
  • CMDENTRYPOINT có 3 phiên bản
    CMD command args1 args2 -> gọi /bin/bash -c 'command args1 args2'
    CMD [args1, args2] -> gọi python hoặc bin/bash để chạy các args1, tuỳ theo file
    CMD [command, arg1, arg2] -> gọi command arg1 arg2
    
  • CMD python run.py
    
    Yêu cầu container chạy lệnh /bin/sh -c 'python run.py' khi container vừa khởi tạo
  • CMD ["start.sh"]
    
    Chạy lệnh /bin/sh start.sh khi container vừa khởi tạo
  • CMD ["run.py"]
    
    Chạy lệnh python run.py khi container vừa khởi tạo. Lưu ý, một vài image như alpine có thể sẽ không đủ thông minh để gọi lệnh python. Mà mặc định là /bin/bash
  • CMD ["python", "run.py"]
    
    Chạy lệnh python run.py khi docker vừa khởi tạo
  • Cú pháp CMDENTRYPOINT tương tự nhau. Nên thay CMD = ENTRYPOINT ta sẽ được cú pháp không thể thay thế khi khởi tạo container
  • ENTRYPOINT python run.py
    ENTRYPOINT ["start.sh"]
    ENTRYPOINT ["python", "run.py"]
    
  • Tuy nhiên, hiện tại Docker đã cho phép thay thế ENTRYPOINT bằng option --entrypoint
  • docker run --entrypoint=<command> <image>
    

  • Để set biến môi trường, ta có thể dùng lệnh ENV ngay trong Dockerfile
  • ENV KEY=VALUE
    
  • ENV MY_NAME=foo
    ENV MY_FEN=bar
    
13 Likes

Ví dụ đơn giản về Dockerfile chạy chương trình python

FROM python:3.9-alpine

WORKDIR /app

COPY requirement.txt .
RUN pip3 install -r requirement.txt

COPY . . # copy toàn bộ code vô docker container
ENTRYPOINT ["python", "-u", "software.py"]

Ví dụ phức tạp hơn về Dockerfile chạy nginx (nguồn đi lượm được)

# Base on offical NGINX Alpine image
FROM ubuntu:18.04

# install nginx
RUN apt-get update -y
RUN apt-get install -y software-properties-common
RUN add-apt-repository -y ppa:nginx/stable
RUN apt-get update -y
RUN apt-get install -y nginx

# deamon mode off
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf
RUN chown -R www-data:www-data /var/lib/nginx

# Expose the listening port
EXPOSE 80 443

# work dir
WORKDIR /etc/nginx

VOLUME ["/etc/letsencrypt"]

CMD /bin/bash -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g "daemon off;"'
10 Likes

Docker này hay ho nhưng nói chung là không dành cho dân amateur. Mình chẳng hiểu thế quái nào mà có Docker trong máy, dẫn đến việc nó chiếm gần như hầu hết các port dưới 20000 trên máy hoặc làm gì đó với hầu hết các port mà khiến cho các dịch vụ khác không dùng Docker là không thể start. Giờ lại chưa có thời gian để đào sâu Docker nên giờ cứ mỗi khi bật máy tính hoặc định chạy cái gì đó lại phải stop Docker trước tiên. Nó stop cũng chậm ơi là chậm, đúng là… bỗng nhiên mua một cái… tàu sân bay đặt trong sân nhà để rồi khóc với nó.

5 Likes

mình hồi đó cũng bị vầy, cách fix là tắt hết mọi container, cho nó stop hẳn rồi hẳn tắt docker, vì nếu bạn tắt ngang docker luôn thì mỗi lần docker bật lại nó sẽ tự động start những container bị tắt ngang. Mình cũng không rõ là cách này có đúng với mọi máy không nhưng nó đúng với mac của mình, còn để tắt hết mọi container trong 1 lệnh thì bạn có thể dùng docker kill $(docker ps -q)

5 Likes

A post was split to a new topic: Mục đích Docker được tạo ra là gì?

Cám ơn bạn đã chia sẻ mẹo hay. Sau khi loay hoay đủ kiểu vẫn còn đó Docker bực cả mình, nên giờ đây mình đã gỡ bỏ Docker. Docker được cài trên một cái máy cũ khác, chạy Linux phiên bản dành cho server để thi thoảng kết nối vào nó vọc chơi. Còn máy tính đang dùng mỗi ngày chỉ có hệ điều hành mà không cài cắm gì thêm, mọi “phần mềm” đều truy cập bằng Firefox.

5 Likes

Sao tớ cài trên win lẫn linux có vấn đề gi đâu ta :neutral_face: Dễ xài,đỡ ô nhiễm máy tính.

4 Likes

sugoi quá Rồng ơi, tiện bài Docker, bạn có thể bổ sung thêm post cho phần Docker compose đc không?

5 Likes

ok luôn nha :ok_hand: để mình soạn

5 Likes

Vấn đề là mình không có máy tính mạnh, mà chỉ có gần chục máy tính yếu (mua lại một tiệm NET thanh lý). Hiện tại mình chưa biết cách nào để kết nối các máy tính yếu này thành 1 máy tính mạnh, không rõ là 10 cái máy core I3 có nối lại mạnh bằng 1 cái core I7 hay core i9 gì đó không. Theo mình nghĩ, có lẽ cả chùm máy mình nếu nối lại được cũng chỉ giống cả trường đại học đánh cờ với đại kiện tướng Lê Quang Liêm, vẫn thua anh ấy, số đông không có ý nghĩa gì nhiều lúc này.

1 Like

Docker có lẽ là một trong những stack mà mọi người dễ hiểu lầm nhất. Thông thường khi mình đọc những bài trên DNH hay nhiều diễn đàn khác thường so sánh docker và máy ảo, tuy nhiên mình nghĩ so sánh docker và package manager có lẽ hợp lý hơn.

Để tiện hình dung và so sánh giống như trong java, việc sử dụng docker giống như bạn compile và chạy .war file vậy. Bản thân docker thời kỳ đầu sử dụng cgroups (under the hood) và cái mang lại tiện dụng cho người dùng (docker runtime) thì khá đơn giản - thậm chí nếu bạn nào giỏi về bashscript thì có thể viết docker runtime-POC dưới 200 LOC. Mình cũng đã từng viết 1 python script 20 dòng mô tả docker runtime trong 1 buổi seminar trong công ty, nó đơn giản hơn bạn nghĩ.

Có lẽ nếu có thời gian mình sẽ viết 1 bài cụ thể hơn!

6 Likes
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?