Docker: Chưa biết gì đến biết dùng ( Phần 4 - Một số trick tối ưu và lưu ý )

1. Mở đầu

  • Chào các bạn, sau khi đi qua ba phần đầu của series: Docker - Chưa biết gì đến biết dùng
  • Chúng ta đã tìm hiểu vai trò, cách sử dụng Dockerfilke && docker-compose.yml để ứng dụng Docker vào trong dự án.

  • Hôm nay, chúng ta sẽ tiếp tục tìm hiểu một số cách để tối ưu công cụ này nhé !

  • Bài viết tiếp tục dựa trên Rails framework để tìm hiểu, nhưng tác giả sẽ cố gắng để viết tổng quát vấn đề.

2. Docker-compose up

  • Sau khi chúng ta gõ docker-compose up thì

    • Câu lệnh này sẽ pull / build tất cả cả các images mà chúng ta đã định nghĩa

    • Trong trường hợp máy đã có sẵn image thì sẽ được skip bước này

    • Tiếp theo là run imagecreate container.

    • Rồi đính kèm chúng với các service tương ứng (Luôn nhớ rằng mỗi container ứng với một máy ảo)

    • Câu lệnh này sẽ tổng hợp logs của tất cả các container và hiển thị lên Terminal

    • đâymysql serviceapp service nên có logs của 2 bác này, màu hiển thị cũng khác nhau cho chúng ta dễ phân biệt.

    • Khi muốn tắt service thì nhấn tổ hợp phím Ctrl + C

    • Nếu như bạn dùng docker-compose up -d thì các containers sẽ chạy ngầm, màn hình Terminal sẽ không hiển thị logs


  • Với cách triển khai này thì có chút nhược điểm như sau:

    • Như bạn thấy ở trên, khi Ctrl + C thì sẽ tắt tất cả service,

    • Khi docker-compose up thì lại bật tất cả chúng lên.

    • Cứ tắt bật, tắt bật tất cả service liên tục như thế, trong project này chỉ có 2 service nên có vẻ chưa hề hấn gì nhưng nếu số lượng service lớn hơn (ví dụ mysql + redis + nginx ....) thì cũng mất kha khá thời gian đấy.

    • Hơn nữa, khi chúng ta debug code trên Terminal, kiểu như thế này

    • Thì docker-compose up sẽ không dừng hiển thị logs để chúng ta kiểm tra giá trị của các biến chẳng hạn,

    • Mà bỏ qua một cách lạnh lùng, như thế này.

    • Bởi vì docker-compose up hiển thị logs tổng hợp của tất cả service

    • Không vì một service nào mà dừng lại cả, như vậy là chúng ta không debug được.


-> Giải pháp là gì ?

  • Thay vì bật tất cả containers lên cùng một lúc

  • Chúng ta sẽ bật các background containers lên trước (ví dụ: mysql, redis ...)

  • Sau đó mới bật main container (ví dụ: app).

  • Khi stop thì ta chỉ stop main container thôi, các background containers vẫn chạy ngầm bên dưới.

  • Câu lệnh

    • Start background container( chỉ 1 lần khi mới mở máy tính lên )

       docker-compose up -d mysql 
       docker-compose up -d redis
       docker-compose up -d woker 
      
    • Start main container

       docker-compose run --rm -p 3000:3000 app rails s
      

    -> Với cách này chúng ta sẽ luôn có background service already running , thời gian khởi động ứng dụng sẽ giảm xuống.

    -> docker-compose run sẽ dừng để bạn có thể debug, --rm sẽ xóa chính container này đi khi bạn stop nó.

3. Makefile

  • Phải công nhận là gõ những dòng docker-compose up -d ... nhiều như vậy thật là mệt.

  • Chưa kể docker-compose build, up rồi down, run ...

  • Vậy thì hãy dùng Makefile, công dụng của nó thì có nhiều hơn nhưng ở đây mình chỉ ứng dụng nôm na giống như Git alias vậy, không cần phải ghi nhớ và gõ nhiều câu lệnh.

  • Thay vì phải gõ từng câu lệnh một

    docker-compose up -d mysql 
    docker-compose up -d redis
    docker-compose up -d worker 
    

    hoặc ngắn hơn

    docker-compose up -d mysql redis worker
    

    thì hãy viết vào trong Makefile:

    up:
       docker-compose up -d mysql redis worker
    

    dev: 
        docker-compose run --rm -p 3000:3000 app rails s
    
  • Khi đó, trên Terminal gõ make up để start các background containers, sau đó gõ make dev để start main container

  • Mình có push code mẫu lên GitHub

  • Hơn nữa, khi có một member mới vào dự án và chưa rõ về Docker (có thể là member của team front end chẳng hạn), khi support setup project, bạn chỉ cần hướng dẫn.

    • Chạy make up để bật các tiến trình nền.
    • Chạy make dev để start project.
    • Chạy make test để test code trước khi gửi pull request.

Mà bản thân họ không cần phải có quá nhiều kiến thức về Docker, khá là tiện lợi phải không ✌️

3. Depends_on && links

  app:
    build: .
    container_name: app
    depends_on:
      - mysql
      - redis

hoặc

  app:
    build: .
    container_name: app
    links:
      - mysql
      - redis
  • depends_onlinks được dùng để thể hiện sự phụ thuộc giữa các service, nó tạo ra các hành vi:

    • docker-compose up sẽ khởi động các service theo thứ tự phụ thuộc, ở đây sẽ là khởi động mysqlredis trước.
    • docker-compose up app - tức là khi bạn chỉ khởi động 1 service đơn lẻ thì service mysqlredis vẫn sẽ được khởi động.
  • Sự khác nhau giữa hai đồng chí này, thậm chí links cuối cùng có thể được gỡ bỏ, thay vào đó các service nằm trong cùng một network thì có thể tự tìm thấy nhau.

  • Thì đấy, bạn có thể giải quyết vấn đề này bằng cách sử dụng depends_on nhưng nhược điểm thì đã nên ở trên, cứ bật lên tắt xuống mất khá nhiều thời gian, trong thực tế service mysql có thể mất tới gần 20s để khởi động.

  • Thậm chí khi mysql container đã chạy sẵn mà bạn run container app lên thì Docker vẫn tạo mới một mysql container nữa chứ không sử dụng mysql container đó.

4. CMD && entrypoint

5. Layers và images

  • Docker image được xây dựng dựa trên các layers xếp chồng, giống như việc bạn xếp nhiều viên gạch chồng lên nhau vậy.

  • Cùng xem cách Docker build image

    • mysql uses an image, skipping: Container mysql sử dụng image có sẵn bên không cần build image nữa -> skipping

    • Building app: Bắt đầu build image cho container app.

    • Step 1/15, Step 2/15, Step 3/15 ... Từng câu lệnh trong Dockerfile sẽ được thực thi và sẽ tạo ra các layers tương ứng.

      Nếu câu lệnh trước đó đã được thực thi và tạo layer thì Docker sẽ sử dụng layer cũ đó chứ không tạo layer mới nữa, giúp giảm thời gian build image và nếu ở một layer có sự thay đổi thì kể từ layer đó trở về sau, tất cả sẽ được build lại.

  • Dùng docker images -a để kiểm ra danh sách các images nhé.

Oài, sao nhiều none image vậy ? Chúng là gì, sao dung lượng của chúng lớn vậy ? Nó sẽ tiêu thụ nhiều không gian ổ cứng à ?

  • Thực ra none image chính là những layers, hãy xem cách Docker pull images về như thế nào.

  • Cũng tương tự như khi chúng ta build image vậy, từng layer được xây dựng theo mô hình cha con, sinh sau đẻ muộn hơn thì là layer con, kế thừa từ layer cha, tất cả đều được đặt tên là <none> như bức ảnh ở trên, đến layer cuối cùng thì mới đầy đủ image của chúng ta và đặt tên chính xác. Cùng xem kích thước các layers tăng dần kìa.

    • Dòng ---> Using cache xuất hiện mỗi khi bạn build image chính là tái sử dụng các layers. Những layers mà không được tái sử dụng nữa được gọi là dangling images, tạm dịch là những image lơ lửng -> nó không trỏ tới images nào cả.

    • Ủa lạ hây, phải gọi mà dangling layers mới đúng chứ nhỉ ? Các tài liệu mà mình tham khảo chưa thấy có khái niệm dangling layers, chỉ có dangling images thôi. Mà câu lệnh docker images -a cũng trả về cả danh sách images && danh sách layers nữa, -a là viết tắt của all, thế layers với imagessêm sêm nhau à. Cũng có thể hiểu như vậy, vì nếu trong Dockerfile, ta xóa bỏ đi vài dòng cuối, thì layers ngoài cùng đó trở thành images còn gì 😄 😄 😄

6. Một vài lưu ý nhỏ

Có một chút lưu ý khi sử dụng Docker như sau:

  • Quy định phiên bản của image chi tiết nhất có thể.

    • Nếu bạn define

      services:
        mysql:
          image: mysql
          container_name: mysql
      

      thì mặc định, Docker sẽ pull image mysql phiên bản mới nhất về, ở thời điểm bạn viết Dockerfile thì có thể mysql phiên bản mới nhất là 5.7 nhưng đến khi người khác settup project thì có thể nó đã lên tới phiên bản 8.0

    • Và ở giữa phiên bản có những sự thay đổi nhất định, có thể một hàm, phương thức nào đó hoạt động tốt ở mysql 5.7 nhưng không hoạt động tốt ở mysql 8.0, ví dụ vậy. Như thế sẽ dễ phát sinh bug tiềm ẩn và cũng vi phạm tính đồng nhất môi trường mà Docker lấy làm tôn chỉ.

  • Ở Dockerfile, các phần dễ thay đổi thì thực hiện về sau.

    • Như lý thuyết về phần layer đã nhắc tới, nếu ở một layer có sự thay đổi thì kể từ layer đó trở về sau, tất cả sẽ được build lại

    • Do vậy, những câu lệnh nào có khả năng thay đôỉ cao, bạn hãy đặt nó xuống dưới cùng.

  • Hãy xóa bỏ những layers không cần thiết

    • Những dangling images không còn hữu ích nữa, chúng cũng không được Docker tự động xóa, mà chúng ta sẽ xóa thủ công nó đi để giải phóng bộ nhớ. Cũng chưa rõ các phiên bản sau này, Docker có tự động xử lý việc này không

    • Hôm nay là 24/03/2019, Dương lịch, Linux OS

      docker -v -->> Docker version 18.09.0, build 4d60db4

      1. Danh sách images:

      1. Chỉnh sửa nội dung 1 dòng lệnh gần cuối của Dockerfile (giữ nguyên số lượng câu lệnh) và tiến hành build image thì thấy từ vị trí thay đổi trở đi không còn được sử dụng cache nữa

      1. Kiểm danh sách images thì thấy số lượng none images tăng lên -> Oh, đúng rồi ^_^

      1. Kiểm tra danh sách image "lơ lửng", xóa nó đi và kiểm tra lại

      Danh sách none images chỉ hiển thị id của layer tại vị trí mà bạn thay đổi, khi xóa nó thì sẽ xóa tất cả layers con đi kèm.

      Ngoài ra có thể tham khảo lệnh docker system prune để dọn dẹp cho Docker

5. Updating ...

  • Trong phần 5 chúng ta sẽ cùng tìm hiểu về deploy with Docker nhé (bao gồm cấu hình nginx, sử dụng haproxy ....) Mời mọi người đón đọc.