Posts Docker cơ bản và thực hành
Post
Cancel

Docker cơ bản và thực hành

Bài viết được tham khảo và ghi chú lại từ series thực hành docker tại Viblo. Link series tại đây: Thực hành Dokcer từ căn bản

I. Cài đặt docker và một số khái niệm cơ bản

Phần cài đặt có thể tham khảo trên doc

DOCKER LÀ GÌ

Các điểm lợi từ khi ứng dụng được đóng gói (containerized app)

  • Build 1 lần dùng mọi nơi ( works on my machine problem solved )
  • Môi trường độc lập
  • Phát triển dễ dàng
  • Tính mở rộng (container orchestration) -> Trước kia có máy ảo cũng có tính chất tương tự thế nhưng tại sao lại có cả docker ? Docker ảo hóa ở mức hệ điều hành còn máy ảo thì ở mức phần cứng

IMAGES VÀ CONTAINERS

”"”Since we already know what containers are it’s easier to explain images through them: Containers are instances of images. A basic mistake is to confuse images and containers.””

Docker images là một file (read-only) không thể thay đổi (thực ra có thể thêm các layers bằng việc bắt đầu từ một base images). Images sẽ được tạo ra bởi việc build một file hướng dẫn (instruction file) gọi là DockerFile

Container được tạo ra từ image (read-write). Nó sẽ chứa những thứ cần thiết cho việc chạy chương trình và là một môi trường cô lập trên máy chủ có thể tương tác với các máy khác và chính nó thông qua các giao thức TCP/UDP

*Docker run vs Docker start: The command docker run is used to only START a container for the very first time. To run an existing container what you need is docker start $container-name *

**docker run = create docker container + start docker container

II.Chia sẻ dữ liệu host-container, container-container

Chia sẻ dữ liệu tức là các container muốn thao tác hoặc sử dụng tới những dữ liệu trên máy host hay trên các container khác

Chia sẻ dữ liệu host-container

Giả sử ta vừa tạo dữ liệu trên máy host có đường dẫn X

1
docker run -ti -v /home/viethoang/petproject:/home/petproject_container ubuntu

Giải thích các tham số -ti: cung cấp thao tác và kết nối trên terminal -v: chia sẻ dữ liệu giữa host và container. đường dẫn đầu tiên là của máy host, cái sau dấu : là của container ubuntu là image hệ điều hành ubuntu

Sau khi chạy lệnh xong ta vào được terminal của container và ta thấy thư mục “petproject” trên máy host ánh xạ với thư mục “petproject_container”. Mọi thay đổi trong thư mục ở host sẽ ảnh hưởng đến container và ngược lại, ví dụ thêm mới hay xóa. Điều đó cũng tương tự khi ở container. Tuy nhiên nếu ta xóa container đi thì dữ liệu trên host vẫn còn

Chia sẻ dữ liệu giữa 2 container

Bây giờ ta có dữ liệu trên máy host muốn chia sẻ cho cả 2 container. Ta đã có 1 container được chia sẻ từ host với id như hình dưới nên để chia sẻ dữ liệu từ host tới container còn lại ta có thể dùng lệnh:

1
docker run -ti --name C2 --volume-from a8232b31eb20 ubuntu 

Quản lý ổ đĩa để chia sẻ dữ liệu cho các container

Khi container được tạo ra thì ổ đĩa cũng được tạo và gán vào container đó,khi container bị xóa thì những ổ đĩa vẫn còn ở đó chỉ mất đi khi ta xóa trực tiếp

1. Tạo ổ đĩa

Lệnh docker volume ls để xem các ổ đĩa Tạo mới một ổ đĩa tên D1 : docker volume create D1

2. Gán ổ đĩa vào container

Ta thực hiện câu lệnh:

1
 docker run -it --name C3 --mount source=D1,target=/home/disk ubuntu

Tham số –mount là để gán thêm ổ đĩa khi tạo container

source=D1 là tham số ổ đĩa cần gán vào container

target=/home/disk với /home/disk là thư mục ánh xạ ổ đĩa D1 vào container

3. Tạo ổ đĩa ánh xạ trên máy host

Bây giờ ta muốn một ổ đĩa ánh xạ nên một folder cố định có sẵn trên host

1
docker volume create --opt device=/home/viethoang/petproject --opt type=none --opt o=bind D2

Kiểm tra lại:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
~ docker volume inspect D2
[
    {
        "CreatedAt": "2021-07-28T16:19:19+07:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/D2/_data",
        "Name": "D2",
        "Options": {
            "device": "/home/viethoang/petproject",
            "o": "bind",
            "type": "none"
        },
        "Scope": "local"
    }
]

  1. Tạo container kèm gán ổ đĩa ánh xạ trên local
1
docker run -it --name C4 -v D2:/home/disk ubuntu

Container C4 được tạo từ image ubutun có gán ổ đĩa D2 và ổ đĩa được ánh xạ trên 1 folder ở host trong phần trước

1
2
3
4
5
6
7
~ docker exec -it  C4 bash
root@976236084a9c:/# ls /home/disk/
20202  20203  Algorithm-application  DE  Non-Trivial-DE  aia  ktlt
root@976236084a9c:/# 



III. Tạo và quản lý network trong docker

Một số khái niệm và default network

Khái niệm

Docker network đảm bảo cho các container kết nối vào network:

  • các container cùng một network có thể liên lạc với nhau qua tên và cổng mà nó lắng nghe trên mạng đó
  • Kết nối 1 hay nhiều host
  • Kết nối container với các mạng khác nằm ngoài docker
  • Kết nối giữa các cụm container

Default Network

Có 3 loại networks được tự động tạo ra trong docker là bridge, none, host ta có thể xem bằng lệnh docker network ls

bridge đây là driver mạng mặc định của Docker, bridge là driver phù hợp nhất cho việc giao tiếp các containers độc lập. các container trong cùng mạng có thể giao tiếp với nhau qua địa chỉ IP, nếu không chỉ định driver thì bridge sẽ là driver mạng mặc định khi khởi tạo.

none driver cung cấp cho một container networking stack và không gian mạng riêng của nó, thường được dùng với mạng tùy chỉnh, driver này không thể dùng trong cụm swarm.

host dùng khi container cần giao tiếp với host và sử dụng trực tiếp mạng của máy chủ đang chạy

Tạo kết nối container với container

Thử tạo 2 container và nằm cùng trên 1 network là bridge để 2 container kết nối được với nhau

1
 docker run -it --rm --name B1 busybox`

Sau khi tạo kiểm tra trong mạng bridge đã xuất hiện container B1 chưa

1
 docker network inspect bridge

Hoặc kiểm tra bằng container

1
docker inspect B1

Sau đó tạo một container B2 tương tự rồi kiểm tra và thử ping từ B1 đến B2.

1
docker attach B1

Lệnh trên sẽ vào container B1 và dùng terminal trong đó để ping

Tạo kết nối giữa host và container

1
docker run -it --name B3 -p 8888:80 busybox

-p 8888:80 tức là ánh xạ cổng 8888 trên host với cổng 80 trên container, nghĩa là cổng 8888 trên host sẽ hiển thị được nội dung trên cổng 80 của container

Tạo docker network

Mục đích là để tạo các mạng tách riêng cho các container có thể sử dụng tách biệt với nhau 1: Tạo network

docker network create --driver bridge network1

Lệnh trên tạo ra mạng network1 ta kiểm tra bằng lệnh docker network ls

2: Xóa network

docker network rm name_network

3: Tạo container kết nối vào một network được chỉ định

docker run -it --name B4 --network network1 busybox

4: Kêt nối một container đang chạy với một mạng khác

Ví dụ: ta có 2 network là network1 và network2 có một container B5 đang kết nối với mạng network1 và ta muốn container này kết nối với cả network2 thì ta chạy lệnh

docker network connect network2 B5

Lệnh trên là kết nối container B5 vào mạng network2

IV. Dockerfile, build image từ dockerfile

Giới thiệu về Dockerfile

Dockerfile là một file text có tác dụng chứa các hướng dẫn để docker có thể build image

Dockerfile = comments + command + arguments

Docker image được tạo thành từ nhiều layer và mặc định nó bắt đầu từ base layer. Các layer này được tạo ra từ các lệnh (command) trong dockerfile

Cách viết Dockerfile

Cú pháp của một Dockerfile

Cú pháp chung của một Dockerfile có dạng:

INSTRUCTION arguments
  • INSTRUCTION là tên các chỉ thị có trong Dockerfile, mỗi chỉ thị thực hiện một nhiệm vụ nhất định, được Docker quy định. Khi khai báo các chỉ thị này phải được viết bằng chữ IN HOA.
  • Một Dockerfile bắt buộc phải bắt đầu bằng chỉ thị FROM để khai báo đâu là image sẽ được sử dụng làm nền để xây dựng nên image của bạn.
  • aguments là phần nội dung của các chỉ thị, quyết định chỉ thị sẽ làm gì.

Ví dụ:

FROM alpine:3.4

RUN apk update && \
    apk add curl && \
    apk add git && \
    apk add vim

Các chỉ thị chính trong Dockerfile

FROM

Chỉ định rằng image nào sẽ được dùng làm image cơ sở để quá trình build image thực thiện các câu lệnh tiếp theo. Các image base này sẽ được tải về từ Public Repository hoặc Private Repository riêng của mỗi người tùy theo setup.

Cú pháp:

FROM <image> [AS <name>]
FROM <image>[:<tag>] [AS <name>]
FROM <image>[@<digest>] [AS <name>]

Chỉ thị FROMbắt buộc và phải được để lên phía trên cùng của Dockerfile.

Ví dụ:

FROM ubuntu
hoặc
FROM ubuntu:latest

LABEL

Chỉ thị LABEL được dùng để thêm các thông tin meta vào Docker Image khi chúng được build. Chúng tồn tại dưới dạng các cặp key - value, được lưu trữ dưới dạng chuỗi. Có thể chỉ định nhiều label cho một Docker Image, và tất nhiên mỗi cặp key - value phải là duy nhất. Nếu cùng một key mà được khai báo nhiều giá trị (value) thì giá trị được khai báo gần đây nhất sẽ ghi đè lên giá trị trước đó.

Cú pháp:

LABEL <key>=<value> <key>=<value> <key>=<value> ... <key>=<value> 

Bạn có thể khai báo metadata cho Image theo từng dòng chỉ thị hoặc có thể tách ra khai báo thành từng dòng riêng biệt.

Ví dụ:

LABEL com.example.some-label="lorem"
LABEL version="2.0" description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."

Để xem thông tin meta của một Docker Image, ta sử dụng dòng lệnh:

docker inspect <image id>

Ví dụ:

MAINTAINER

Chỉ thị MAINTAINER dùng để khai báo thông tin tác giả người viết ra file Dockerfile.

Cú pháp:

MAINTAINER <name> [<email>]

Ví dụ:

MAINTAINER NamDH <namduong3699@gmail.com>

Hiện nay, theo tài liệu chính thức từ bên phía Docker thì việc khai báo MAINTAINER đang dần được thay thế bằng LABEL maintainer bới tính linh hoạt của nó khi ngoài thông tin về tên, email của tác giả thì ta có thể thêm nhiều thông tin tùy chọn khác qua các thẻ metadata và có thể lấy thông tin dễ dàng với câu lệnh docker inspect ....

Ví dụ:

LABEL maintainer="namduong3699@gmail.com"

RUN

Chỉ thị RUN dùng để chạy một lệnh nào đó trong quá trình build image và thường là các câu lệnh Linux. Tùy vào image gốc được khai báo trong phần FROM thì sẽ có các câu lệnh tương ứng. Ví dụ, để chạy câu lệnh update đối với Ubuntu sẽ là RUN apt-get update -y còn đối với CentOS thì sẽ là Run yum update -y. Kết quả của câu lệnh sẽ được commit lại, kết quả commit đó sẽ được sử dụng trong bước tiếp theo của Dockerfile.

Cú pháp:

RUN <command>
RUN ["executable", "param1", "param2"]

Ví dụ:

RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
-------- hoặc --------
RUN ["/bin/bash", "-c", "echo hello"]

Ở cách thức shell form bạn có thể thực hiện nhiều câu lệnh cùng một lúc với dấu \:

FROM ubuntu
RUN apt-get update
RUN apt-get install curl -y

hoặc

FROM ubuntu
RUN apt-get update; \
    apt-get install curl -y

ADD

Chỉ thị ADD sẽ thực hiện sao chép các tập, thư mục từ máy đang build hoặc remote file URLs từ src và thêm chúng vào filesystem của image dest.

Cú pháp:

ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

Trong đó:

  • src có thể khai báo nhiều file, thư mục, …
  • dest phải là đường dẫn tuyệt đối hoặc có quan hệ chỉ thị đối với WORKDIR.

Ví dụ:

ADD hom* /mydir/
ADD hom?.txt /mydir/
ADD test.txt relativeDir/

Bạn cũng có thể phân quyền vào các file/thư mục mới được copy:

ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/

COPY

Chỉ thị COPY cũng giống với ADD là copy file, thư mục từ <src> và thêm chúng vào <dest> của container. Khác với ADD, nó không hỗ trợ thêm các file remote file URLs từ các nguồn trên mạng.

Cú pháp:

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

ENV

Chỉ thị ENV dùng để khai báo các biến môi trường. Các biến này được khai báo dưới dạng key - value bằng các chuỗi. Giá trị của các biến này sẽ có hiện hữu cho các chỉ thị tiếp theo của Dockerfile.

Cú pháp:

ENV <key>=<value> ...

Ví dụ:

ENV DOMAIN="viblo.asia"
ENV PORT=80
ENV USERNAME="namdh" PASSWORD="secret"

Ngoài ra cũng có thể thay đổi giá trị của biến môi trường bằng câu lệnh khởi động container:

docker run --env <key>=<value>

ENV chỉ được sử dụng trong các command sau:

  • ADD
  • COPY
  • ENV
  • EXPOSE
  • FROM
  • LABEL
  • STOPSIGNAL
  • USER
  • VOLUME
  • WORKDIR

CMD

Chỉ thị CMD định nghĩa các câu lệnh sẽ được chạy sau khi container được khởi động từ image đã build. Có thể khai báo được nhiều nhưng chỉ có duy nhất CMD cuối cùng được chạy.

Cú pháp:

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

Ví dụ:

FROM ubuntu
CMD echo Viblo

USER

Có tác dụng set username hoặc UID để sử dụng khi chạy image và khi chạy các lệnh có trong RUN, CMD, ENTRYPOINT sau nó.

Cú pháp:

USER <user>[:<group>]
hoặc
USER <UID>[:<GID>]

Ví dụ:

FROM alpine:3.4
RUN useradd -ms /bin/bash namdh
USER namdh

Tạo image với Dockerfile

Phần trên mình đã đi qua được cấu trúc cũng như các chỉ thị chính của một Dockerfile. Bây giờ chúng ta sẽ thực hành build một image bằng cách viết một Dockerfile đơn giản. Lưu ý rằng trước khi bắt đầu, hãy chắc chắn rằng máy của bạn đã được cài đặt sẵn Docker. Nếu chưa, bạn có thể tham khảo hướng dẫn cài đặt ở đây.

Tạo Dockerfile

Ví dụ 1

Ở bước này, chúng ta sẽ tạo một đường dẫn mới cho Dockerfile.

mkdir myproject && cd myproject

Tiếp theo là tạo Dockerfile, lưu ý rằng tên của Dockerfile phải đúng là “Dockerfile” và không có phần đuôi mở rộng cho loại file này. Nếu không đặt tên đúng, khi build hệ thống sẽ báo lỗi là không tìm thấy file.

touch Dockerfile

Sau khi đã tạo xong Dockerfile, ta tiền hành nhập các chỉ thị:

FROM alpine
CMD ["echo", "Hello world!"]

Như bạn thấy, nội dung của Dockerfile ở trên chỉ chứa 2 chỉ thị FROMCMD, CMD chứa câu lệnh echo và sẽ in ra màn hình dòng chữ “Hello world!” khi mà container được khởi động từ image đã build từ Dockerfile này. Ta tiến hành tạo image với câu lệnh:

docker build -t <tên image> .

Cuối cùng, ta chạy lệnh docker run <imageID> để tạo và chạy container. Kết quả thu được sẽ là dòng chữ “Hello world!” được in ra trên màn hình.

V. Docker-compose

Khái niệm

Là công cụ giúp ta thiết lập và quản lý nhiều container, network, volume (gọi chung là các service) và thiết lập cấu hình cho các service một cách nhanh chóng và đơn giản bằng việc chạy theo các chỉ định trong file docker-compose.yml

Những chức năng chính

  • Tạo và quản lý nhiều môi trường độc lập trong một máy host đảm bảo độc lập các phân vùng ổ nhớ tránh say ra xung đột
  • Chỉ tạo lại container thay đổi, nhận biết các container không thay đổi và sử dụng lại
  • Định nghĩa và sử dụng biến môi trường trong file YAML

Docker-compose.yml

Là một file lưu dạng yaml, file này lưu các chỉ thị để docker compose đọc file này và thực thi các chỉ thị đó, các chỉ thị như tạo container từ image, tạo network, cấu hình cho các dịch vụ.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: "3.9"  # optional since v1.27.0
services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/code
      - logvolume01:/var/log
    links:
      - redis
  redis:
    image: redis
volumes:
  logvolume01: {}

Thực hành docker-compose

Nhiệm vụ thực hành của chúng ta là định nghĩa docker file để tạo ra các thành phần sau

  • Container MySQL
  • Container HTTP APACHE
  • Container PHP-FPM
  • Network (bridge) để các service trên kết nối vào mạng này
  • Ánh xạ cổng 9999 của máy host vào cổng 80 của máy chủ HTTP

Tạo docker-compose.yml

Tạo thư mục mycode bên trong ta tạo file docker-compose.yml với nội dung như sau (mình có comment giải thích trong phần nội dung file)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
version: "3" #là phiên bản docker composer

#Tạo mạng tên là my-network
networks:
    my-network:
        driver: bridge

# Tạo các dịch vụ (container)
services:

    #Tạo container my-php từ imgae php:latest có kết nối với mạng my-network
    my-php:
        container_name: php-product
        image: 'php:latest'
        hostname: php
        restart: always
        networks:
            - my-network

    #Tạo container my-httpd từ imgae httpd:latest có kết nối với mạng my-network, ánh xạ cổng 9999 của máy host vào cổng 80
    my-httpd:
        container_name: c-httpd01
        image: 'httpd:latest'
        hostname: httpd
        restart: always
        networks:
            - my-network
        ports:
            - "9999:80"
            - "443:443"
            
     #Tạo container my-mysql từ imgae mysql:latest có kết nối với mạng my-network,config các biến môi trường
    my-mysql:
        container_name: myql-product
        image: "mysql:latest"
        hostname: mysql
        restart: always
        networks:
            - my-network
        environment:
            - MYSQL_ROOT_PASSWORD=123abc
            - MYSQL_DATABASE=db_site
            - MYSQL_USER=sites
            - MYSQL_PASSWORD=123abc

File trên được chia làm 3 phần

  • phần đầu là khai báo phiên bản docker composer, ở đây mình dùng phiên bản 3

  • Phần tiếp theo là khai báo tạo các mạng, ở đây mình tạo một mạng tên là my-network kiểu mạng là bridge
  • Phần tiếp theo là tạo các services, ở đây là tạo 3 services là 3 container (php, httpd, mysql), các service này đều kết nối đến mạng my-network đã tạo trên phần 2

    • Container my-php tạo từ imgae php:latest có kết nối với mạng my-network
    • Container my-httpd tạo từ imgae httpd:latest có kết nối với mạng my-network, ánh xạ cổng 9999 của máy host vào cổng 80 và cổng 443 với 443 máy host
    • Container my-mysql tạo từ imgae mysql:latest có kết nối với mạng my-network,config các biến môi trường như MYSQL_ROOT_PASSWORD, MYSQL_DATABASE,MYSQL_USER, MYSQL_PASSWORD

Run file docker-compose.yml tạo các service

Tạo xong file docker compose, giờ chúng ta sẽ run file này để tạo ra các service đã định nghĩa.

Ta vào thư mục chứa file docker-compose.yml và chạy lệnh

docker-compose up

Muốn dừng các services đang chạy thì ta dùng lệnh

docker-compose stop

Để kết thúc các services đang chạy và xóa hoàn toàn container ta dùng lệnh

docker-compose down

Theo dõi Logs các services

docker-compose logs [SERVICES]

VI. Docker swarm

VII. Tham Khảo

Các nguồn tham khảo khác:

Tìm hiểu về Dockerfile và tạo Docker Image

Dockerfile tutorial

Devops with Docker

This post is licensed under CC BY 4.0 by the author.