Hoang Tran

Hoang Tran

1660555320

Một Số Phương Pháp Hay Nhất Của Docker Dành Cho Nhà Phát Triển Python

Bài viết này xem xét một số phương pháp hay nhất để làm theo khi viết Dockerfiles và làm việc với Docker nói chung. Trong khi hầu hết các phương pháp được liệt kê áp dụng cho tất cả các nhà phát triển, bất kể ngôn ngữ, một số ít áp dụng cho những người đang phát triển các ứng dụng dựa trên Python.

Dockerfiles

Sử dụng Công trình nhiều giai đoạn

Tận dụng các bản dựng nhiều giai đoạn để tạo hình ảnh Docker gọn gàng hơn, an toàn hơn.

Các bản dựng Docker nhiều giai đoạn cho phép bạn chia các Dockerfiles của mình thành nhiều giai đoạn. Ví dụ: bạn có thể có một giai đoạn để biên dịch và xây dựng ứng dụng của mình, giai đoạn này sau đó có thể được sao chép sang các giai đoạn tiếp theo. Vì chỉ giai đoạn cuối cùng được sử dụng để tạo hình ảnh, các phần phụ thuộc và công cụ liên quan đến việc xây dựng ứng dụng của bạn sẽ bị loại bỏ, để lại hình ảnh sẵn sàng sản xuất theo mô-đun và gọn gàng.

Ví dụ về phát triển web:

# temp stage
FROM python:3.9-slim as builder

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc

COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt


# final stage
FROM python:3.9-slim

WORKDIR /app

COPY --from=builder /app/wheels /wheels
COPY --from=builder /app/requirements.txt .

RUN pip install --no-cache /wheels/*

Trong ví dụ này, trình biên dịch GCC được yêu cầu để cài đặt các gói Python nhất định, vì vậy chúng tôi đã thêm một giai đoạn tạm thời, thời gian xây dựng để xử lý giai đoạn xây dựng. Vì hình ảnh thời gian chạy cuối cùng không chứa GCC nên nó nhẹ hơn và an toàn hơn nhiều.

So sánh kích thước:

REPOSITORY                 TAG                    IMAGE ID       CREATED          SIZE
docker-single              latest                 8d6b6a4d7fb6   16 seconds ago   259MB
docker-multi               latest                 813c2fa9b114   3 minutes ago    156MB

Ví dụ về khoa học dữ liệu:

# temp stage
FROM python:3.9 as builder

RUN pip wheel --no-cache-dir --no-deps --wheel-dir /wheels jupyter pandas


# final stage
FROM python:3.9-slim

WORKDIR /notebooks

COPY --from=builder /wheels /wheels
RUN pip install --no-cache /wheels/*

So sánh kích thước:

REPOSITORY                  TAG                   IMAGE ID       CREATED         SIZE
ds-multi                    latest                b4195deac742   2 minutes ago   357MB
ds-single                   latest                7c23c43aeda6   6 minutes ago   969MB

In summary, multi-stage builds can decrease the size of your production images, helping you save time and money. In addition, this will simplify your production containers. Also, due to the smaller size and simplicity, there's potentially a smaller attack surface.

Order Dockerfile Commands Appropriately

Pay close attention to the order of your Dockerfile commands to leverage layer caching.

Docker caches each step (or layer) in a particular Dockerfile to speed up subsequent builds. When a step changes, the cache will be invalidated not only for that particular step but all succeeding steps.

Example:

FROM python:3.9-slim

WORKDIR /app

COPY sample.py .

COPY requirements.txt .

RUN pip install -r /requirements.txt

In this Dockerfile, we copied over the application code before installing the requirements. Now, each time we change sample.py, the build will reinstall the packages. This is very inefficient, especially when using a Docker container as a development environment. Therefore, it's crucial to keep the files that frequently change towards the end of the Dockerfile.

You can also help prevent unwanted cache invalidations by using a .dockerignore file to exclude unnecessary files from being added to the Docker build context and the final image. More on this here shortly.

So, in the above Dockerfile, you should move the COPY sample.py . command to the bottom:

FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install -r /requirements.txt

COPY sample.py .

Notes:

  1. Always put layers that are likely to change as low as possible in the Dockerfile.
  2. Combine RUN apt-get update and RUN apt-get install commands. (This also helps to reduce the image size. We'll touch on this shortly.)
  3. If you want to turn off caching for a particular Docker build, add the --no-cache=True flag.

Use Small Docker Base Images

Smaller Docker images are more modular and secure.

Building, pushing, and pulling images is quicker with smaller images. They also tend to be more secure since they only include the necessary libraries and system dependencies required for running your application.

Which Docker base image should you use?

Unfortunately, it depends.

Here's a size comparison of various Docker base images for Python:

REPOSITORY   TAG                 IMAGE ID       CREATED      SIZE
python       3.9.6-alpine3.14    f773016f760e   3 days ago   45.1MB
python       3.9.6-slim          907fc13ca8e7   3 days ago   115MB
python       3.9.6-slim-buster   907fc13ca8e7   3 days ago   115MB
python       3.9.6               cba42c28d9b8   3 days ago   886MB
python       3.9.6-buster        cba42c28d9b8   3 days ago   886MB

Mặc dù phiên bản Alpine, dựa trên Alpine Linux , là phiên bản nhỏ nhất, nhưng nó thường có thể dẫn đến việc tăng thời gian xây dựng nếu bạn không thể tìm thấy các tệp nhị phân đã biên dịch hoạt động với nó. Do đó, bạn có thể phải tự tạo các tệp nhị phân, điều này có thể làm tăng kích thước hình ảnh (tùy thuộc vào các phụ thuộc cấp hệ thống được yêu cầu) và thời gian xây dựng (do phải biên dịch từ nguồn).

Tham khảo Hình ảnh cơ sở Docker tốt nhất cho ứng dụng Python của bạnSử dụng Alpine có thể làm cho các bản dựng Docker Python chậm hơn 50 lần để biết thêm lý do tại sao tốt nhất nên tránh sử dụng hình ảnh cơ sở dựa trên Alpine.

Cuối cùng, tất cả là về sự cân bằng. Khi nghi ngờ, hãy bắt đầu với một *-slimhương vị, đặc biệt là trong chế độ phát triển, khi bạn đang xây dựng ứng dụng của mình. Bạn muốn tránh phải liên tục cập nhật Dockerfile để cài đặt các phụ thuộc cấp hệ thống cần thiết khi bạn thêm một gói Python mới. Khi bạn làm cứng ứng dụng của mình và (các) Dockerfile để sản xuất, bạn có thể muốn khám phá bằng cách sử dụng Alpine cho hình ảnh cuối cùng từ một bản dựng nhiều giai đoạn.

Ngoài ra, đừng quên cập nhật hình ảnh cơ sở của bạn thường xuyên để cải thiện bảo mật và tăng hiệu suất. Khi phiên bản mới của hình ảnh cơ sở được phát hành - tức là 3.9.6-slim-> 3.9.7-slim- bạn nên kéo hình ảnh mới và cập nhật các vùng chứa đang chạy của mình để nhận tất cả các bản vá bảo mật mới nhất.

Giảm thiểu số lớp

Bạn nên kết hợp các RUNlệnh COPYADDcàng nhiều càng tốt vì chúng tạo ra các lớp. Mỗi lớp sẽ tăng kích thước của hình ảnh vì chúng được lưu vào bộ nhớ đệm. Do đó, khi số lượng lớp tăng lên, kích thước cũng tăng lên.

Bạn có thể kiểm tra điều này bằng docker historylệnh:

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
dockerfile   latest    180f98132d02   51 seconds ago   259MB

$ docker history 180f98132d02

IMAGE          CREATED              CREATED BY                                      SIZE      COMMENT
180f98132d02   58 seconds ago       COPY . . # buildkit                             6.71kB    buildkit.dockerfile.v0
<missing>      58 seconds ago       RUN /bin/sh -c pip install -r requirements.t…   35.5MB    buildkit.dockerfile.v0
<missing>      About a minute ago   COPY requirements.txt . # buildkit              58B       buildkit.dockerfile.v0
<missing>      About a minute ago   WORKDIR /app
...

Hãy lưu ý các kích thước. Chỉ các lệnh RUN, COPYvà mới ADDthêm kích thước vào hình ảnh. Bạn có thể giảm kích thước hình ảnh bằng cách kết hợp các lệnh nếu có thể. Ví dụ:

RUN apt-get update
RUN apt-get install -y netcat

Có thể được kết hợp thành một RUNlệnh duy nhất:

RUN apt-get update && apt-get install -y netcat

Do đó, việc tạo một lớp duy nhất thay vì hai, làm giảm kích thước của hình ảnh cuối cùng.

Mặc dù giảm số lượng lớp là một ý tưởng hay, nhưng điều đó quan trọng hơn nhiều để điều đó không phải là mục tiêu và tác dụng phụ của việc giảm kích thước hình ảnh và thời gian xây dựng. Nói cách khác, hãy tập trung nhiều hơn vào ba phương pháp trước đó - xây dựng nhiều giai đoạn, thứ tự các lệnh Dockerfile của bạn và sử dụng một hình ảnh cơ sở nhỏ - hơn là cố gắng tối ưu hóa từng lệnh đơn lẻ.

Ghi chú:

  1. RUN, COPYADDmỗi lớp tạo ra.
  2. Mỗi lớp chứa những điểm khác biệt so với lớp trước đó.
  3. Các lớp làm tăng kích thước của hình ảnh cuối cùng.

Lời khuyên:

  1. Kết hợp các lệnh liên quan.
  2. Loại bỏ các tệp không cần thiết trong cùng một RUN stepđã tạo chúng.
  3. Giảm thiểu số lần apt-get upgradechạy vì nó nâng cấp tất cả các gói lên phiên bản mới nhất.
  4. Với các bản dựng nhiều giai đoạn, đừng quá lo lắng về việc tối ưu hóa quá mức các lệnh trong các giai đoạn tạm thời.

Cuối cùng, để dễ đọc, bạn nên sắp xếp các đối số nhiều dòng theo kiểu chữ và số:

RUN apt-get update && apt-get install -y \
    git \
    gcc \
    matplotlib \
    pillow  \
    && rm -rf /var/lib/apt/lists/*

Sử dụng vùng chứa không đặc quyền

Theo mặc định, Docker chạy các quy trình vùng chứa dưới dạng root bên trong vùng chứa. Tuy nhiên, đây là một thực tiễn không tốt vì quá trình chạy dưới quyền root bên trong vùng chứa đang chạy dưới quyền root trong máy chủ Docker. Do đó, nếu kẻ tấn công giành được quyền truy cập vào vùng chứa của bạn, chúng có quyền truy cập vào tất cả các đặc quyền gốc và có thể thực hiện một số cuộc tấn công chống lại máy chủ Docker, như-

  1. sao chép thông tin nhạy cảm từ hệ thống tệp của máy chủ lưu trữ vào vùng chứa
  2. thực hiện các lệnh từ xa

Để ngăn chặn điều này, hãy đảm bảo chạy các quy trình vùng chứa với người dùng không phải root:

RUN addgroup --system app && adduser --system --group app

USER app

Bạn có thể thực hiện thêm một bước nữa và xóa quyền truy cập shell và đảm bảo rằng không có thư mục chính:

RUN addgroup --gid 1001 --system app && \
    adduser --no-create-home --shell /bin/false --disabled-password --uid 1001 --system --group app

USER app

Kiểm chứng:

$ docker run -i sample id

uid=1001(app) gid=1001(app) groups=1001(app)

Ở đây, ứng dụng trong vùng chứa chạy dưới quyền người dùng không phải root. Tuy nhiên, hãy nhớ rằng daemon Docker và bản thân vùng chứa vẫn đang chạy với đặc quyền root. Đảm bảo xem lại Chạy trình nền Docker với tư cách người dùng không phải root để được trợ giúp chạy cả daemon và vùng chứa với tư cách người dùng không phải root.

Thích COPY hơn ADD

Sử dụng COPYtrừ khi bạn chắc chắn mình cần chức năng bổ sung đi kèm ADD.

Sự khác biệt giữa COPYADDlà gì?

Cả hai lệnh đều cho phép bạn sao chép tệp từ một vị trí cụ thể vào hình ảnh Docker:

ADD <src> <dest>
COPY <src> <dest>

Mặc dù chúng trông giống như phục vụ cùng một mục đích, nhưng ADDcó một số chức năng bổ sung:

  • COPYđược sử dụng để sao chép các tệp hoặc thư mục cục bộ từ máy chủ Docker sang hình ảnh.
  • ADDcó thể được sử dụng cho cùng một thứ cũng như tải xuống các tệp bên ngoài. Ngoài ra, nếu bạn sử dụng tệp nén (tar, gzip, bzip2, v.v.) làm <src>tham số, ADDsẽ tự động giải nén nội dung vào vị trí nhất định.
# copy local files on the host  to the destination
COPY /source/path  /destination/path
ADD /source/path  /destination/path

# download external file and copy to the destination
ADD http://external.file/url  /destination/path

# copy and extract local compresses files
ADD source.file.tar.gz /destination/path

Cache các gói Python vào Máy chủ Docker

Khi tệp yêu cầu được thay đổi, hình ảnh cần được xây dựng lại để cài đặt các gói mới. Các bước trước đó sẽ được lưu vào bộ nhớ đệm, như đã đề cập trong Giảm thiểu số lớp . Tải xuống tất cả các gói trong khi xây dựng lại hình ảnh có thể gây ra nhiều hoạt động mạng và mất nhiều thời gian. Mỗi bản xây dựng lại chiếm cùng một lượng thời gian để tải xuống các gói chung trên các bản dựng.

Bạn có thể tránh điều này bằng cách ánh xạ thư mục bộ đệm pip vào một thư mục trên máy chủ. Vì vậy, đối với mỗi lần xây dựng lại, các phiên bản đã lưu trong bộ nhớ cache vẫn tồn tại và có thể cải thiện tốc độ xây dựng.

Thêm một ổ đĩa để chạy docker dưới dạng -v $HOME/.cache/pip-docker/:/root/.cache/piphoặc dưới dạng ánh xạ trong tệp Docker Compose.

Thư mục được trình bày ở trên chỉ mang tính chất tham khảo. Đảm bảo rằng bạn ánh xạ thư mục bộ nhớ cache chứ không phải các gói trang (nơi chứa các gói đã xây dựng).

Di chuyển bộ nhớ cache từ hình ảnh docker sang máy chủ lưu trữ có thể giúp bạn tiết kiệm dung lượng trong hình ảnh cuối cùng.

Nếu bạn đang tận dụng Docker BuildKit , hãy sử dụng gắn kết bộ đệm BuildKit để quản lý bộ đệm:

# syntax = docker/dockerfile:1.2

...

COPY requirements.txt .

RUN --mount=type=cache,target=/root/.cache/pip \
        pip install -r requirements.txt

...

Chỉ chạy một quy trình cho mỗi vùng chứa

Tại sao chỉ nên chạy một quy trình cho mỗi vùng chứa?

Giả sử ngăn xếp ứng dụng của bạn bao gồm hai máy chủ web và một cơ sở dữ liệu. Mặc dù bạn có thể dễ dàng chạy cả ba từ một vùng chứa duy nhất, nhưng bạn nên chạy từng vùng trong một vùng chứa riêng biệt để dễ dàng sử dụng lại và mở rộng quy mô từng dịch vụ riêng lẻ.

  1. Chia tỷ lệ - Với mỗi dịch vụ trong một vùng chứa riêng biệt, bạn có thể chia tỷ lệ một trong các máy chủ web của mình theo chiều ngang khi cần để xử lý nhiều lưu lượng truy cập hơn.
  2. Khả năng tái sử dụng - Có lẽ bạn có một dịch vụ khác cần một cơ sở dữ liệu được lưu trữ. Bạn có thể đơn giản sử dụng lại cùng một vùng chứa cơ sở dữ liệu mà không cần mang theo hai dịch vụ không cần thiết cùng với nó.
  3. Ghi nhật ký - Các thùng chứa ghép nối làm cho việc ghi nhật ký trở nên phức tạp hơn nhiều. Chúng tôi sẽ giải quyết vấn đề này một cách chi tiết hơn ở phần sau của bài viết này.
  4. Tính di động và khả năng dự đoán - Việc tạo các bản vá bảo mật hoặc gỡ lỗi sự cố dễ dàng hơn nhiều khi có ít diện tích bề mặt để làm việc hơn.

Ưu tiên mảng hơn cú pháp chuỗi

Bạn có thể viết lệnh CMDENTRYPOINTtrong Dockerfiles của mình ở cả định dạng mảng (thi hành) hoặc chuỗi (trình bao):

# array (exec)
CMD ["gunicorn", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "main:app"]

# string (shell)
CMD "gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app"

Cả hai đều đúng và đạt được điều gần như giống nhau; tuy nhiên, bạn nên sử dụng định dạng thực thi bất cứ khi nào có thể. Từ tài liệu Docker :

  1. Đảm bảo rằng bạn đang sử dụng biểu mẫu thực thi của CMDENTRYPOINTtrong Dockerfile của mình.
  2. Ví dụ sử dụng ["program", "arg1", "arg2"]not "program arg1 arg2". Việc sử dụng biểu mẫu chuỗi khiến Docker chạy quy trình của bạn bằng cách sử dụng bash, quy trình này không xử lý các tín hiệu đúng cách. Soạn luôn sử dụng biểu mẫu JSON, vì vậy đừng lo lắng nếu bạn ghi đè lệnh hoặc điểm nhập trong tệp Soạn của mình.

Vì vậy, vì hầu hết các trình bao không xử lý tín hiệu cho các quy trình con, nếu bạn sử dụng định dạng trình bao, CTRL-C(tạo ra a SIGTERM) có thể không dừng một quy trình con.

Thí dụ:

FROM ubuntu:18.04

# BAD: shell format
ENTRYPOINT top -d

# GOOD: exec format
ENTRYPOINT ["top", "-d"]

Hãy thử cả hai. Lưu ý rằng với hương vị định dạng shell, CTRL-Csẽ không giết chết quá trình. Thay vào đó, bạn sẽ thấy ^C^C^C^C^C^C^C^C^C^C^C.

Một lưu ý khác là định dạng shell mang PID của shell, không phải của chính quá trình.

# array format
root@18d8fd3fd4d2:/app# ps ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 python manage.py runserver 0.0.0.0:8000
    7 ?        Sl     0:02 /usr/local/bin/python manage.py runserver 0.0.0.0:8000
   25 pts/0    Ss     0:00 bash
  356 pts/0    R+     0:00 ps ax


# string format
root@ede24a5ef536:/app# ps ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 /bin/sh -c python manage.py runserver 0.0.0.0:8000
    8 ?        S      0:00 python manage.py runserver 0.0.0.0:8000
    9 ?        Sl     0:01 /usr/local/bin/python manage.py runserver 0.0.0.0:8000
   13 pts/0    Ss     0:00 bash
  342 pts/0    R+     0:00 ps ax

Hiểu sự khác biệt giữa ENTRYPOINT và CMD

Tôi có nên sử dụng ENTRYPOINT hoặc CMD để chạy các quy trình vùng chứa không?

Có hai cách để chạy lệnh trong vùng chứa:

CMD ["gunicorn", "config.wsgi", "-b", "0.0.0.0:8000"]

# and

ENTRYPOINT ["gunicorn", "config.wsgi", "-b", "0.0.0.0:8000"]

Về cơ bản, cả hai đều làm cùng một việc: Khởi động ứng dụng tại config.wsgimáy chủ Gunicorn và liên kết nó với 0.0.0.0:8000.

CMDDễ dàng bị ghi đè . Nếu bạn chạy docker run <image_name> uvicorn config.asgi, CMD ở trên sẽ được thay thế bằng các đối số mới - ví dụ uvicorn config.asgi:. Trong khi để ghi đè ENTRYPOINTlệnh, người ta phải chỉ định --entrypointtùy chọn:

docker run --entrypoint uvicorn config.asgi <image_name>

Ở đây, rõ ràng là chúng ta đang ghi đè điểm vào. Vì vậy, bạn nên sử dụng ENTRYPOINThết CMDđể tránh vô tình ghi đè lệnh.

Chúng cũng có thể được sử dụng cùng nhau.

Ví dụ:

ENTRYPOINT ["gunicorn", "config.wsgi", "-w"]
CMD ["4"]

Khi được sử dụng cùng nhau như vậy, lệnh được chạy để khởi động vùng chứa là:

gunicorn config.wsgi -w 4

As discussed above, CMD is easily overridden. Thus, CMD can be used to pass arguments to the ENTRYPOINT command. The number of workers can be easily changed like so:

docker run <image_name> 6

This will start the container with six Gunicorn workers rather then four.

Include a HEALTHCHECK Instruction

Use a HEALTHCHECK to determine if the process running in the container is not only up and running, but is "healthy" as well.

Docker tiết lộ một API để kiểm tra trạng thái của quá trình đang chạy trong vùng chứa, cung cấp nhiều thông tin hơn là chỉ xem liệu quá trình có "đang chạy" hay không vì "đang chạy" bao gồm "nó đã hoạt động và hoạt động", "vẫn đang chạy", và thậm chí "bị mắc kẹt trong một số trạng thái lỗi vòng lặp vô hạn". Bạn có thể tương tác với API này thông qua hướng dẫn HEALTHCHECK .

Ví dụ: nếu bạn đang cung cấp một ứng dụng web, bạn có thể sử dụng các thông số sau để xác định xem /điểm cuối có hoạt động hay không và có thể xử lý các yêu cầu cung cấp:

HEALTHCHECK CMD curl --fail http://localhost:8000 || exit 1

Nếu bạn chạy docker ps, bạn có thể thấy trạng thái của HEALTHCHECK.

Ví dụ lành mạnh:

CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS                            PORTS                                       NAMES
09c2eb4970d4   healthcheck   "python manage.py ru…"   10 seconds ago   Up 8 seconds (health: starting)   0.0.0.0:8000->8000/tcp, :::8000->8000/tcp   xenodochial_clarke

Ví dụ không lành mạnh:

CONTAINER ID   IMAGE         COMMAND                  CREATED              STATUS                          PORTS                                       NAMES
09c2eb4970d4   healthcheck   "python manage.py ru…"   About a minute ago   Up About a minute (unhealthy)   0.0.0.0:8000->8000/tcp, :::8000->8000/tcp   xenodochial_clarke

Bạn có thể thực hiện thêm một bước và thiết lập một điểm cuối tùy chỉnh chỉ được sử dụng để kiểm tra tình trạng và sau đó định cấu hình HEALTHCHECKđể kiểm tra dựa trên dữ liệu trả về. Ví dụ: nếu điểm cuối trả về phản hồi JSON {"ping": "pong"}, bạn có thể hướng dẫn HEALTHCHECKxác thực phần thân phản hồi.

Đây là cách bạn xem trạng thái tình trạng kiểm tra sức khỏe bằng cách sử dụng docker inspect:

❯ docker inspect --format "{{json .State.Health }}" ab94f2ac7889
{
  "Status": "healthy",
  "FailingStreak": 0,
  "Log": [
    {
      "Start": "2021-09-28T15:22:57.5764644Z",
      "End": "2021-09-28T15:22:57.7825527Z",
      "ExitCode": 0,
      "Output": "..."

Ở đây, đầu ra được cắt bớt vì nó chứa toàn bộ đầu ra HTML.

Bạn cũng có thể thêm kiểm tra sức khỏe vào tệp Docker Compose:

version: "3.8"

services:
  web:
    build: .
    ports:
      - '8000:8000'
    healthcheck:
      test: curl --fail http://localhost:8000 || exit 1
      interval: 10s
      timeout: 10s
      start_period: 10s
      retries: 3

Tùy chọn:

  • test: Lệnh kiểm tra.
  • interval: Khoảng thời gian cần kiểm tra - tức là kiểm tra mọi xđơn vị thời gian.
  • timeout: Thời gian tối đa để chờ phản hồi.
  • start_period: Khi nào thì bắt đầu kiểm tra sức khỏe. Nó có thể được sử dụng khi các tác vụ bổ sung được thực hiện trước khi các vùng chứa sẵn sàng, chẳng hạn như chạy quá trình di chuyển.
  • retries: Thử lại tối đa trước khi chỉ định thử nghiệm là failed.

Nếu bạn đang sử dụng một công cụ điều phối không phải Docker Swarm - tức là Kubernetes hoặc AWS ECS - thì rất có thể công cụ này có hệ thống nội bộ riêng để xử lý kiểm tra sức khỏe. Tham khảo tài liệu của công cụ cụ thể trước khi thêm HEALTHCHECKhướng dẫn.

Hình ảnh

Hình ảnh phiên bản Docker

Bất cứ khi nào có thể, hãy tránh sử dụng latestthẻ.

Nếu bạn dựa vào latestthẻ (thực sự không phải là "thẻ" vì nó được áp dụng theo mặc định khi hình ảnh không được gắn thẻ rõ ràng), bạn không thể biết phiên bản mã nào của mình đang chạy dựa trên thẻ hình ảnh. Nó làm cho việc quay ngược trở lại khó khăn và dễ dàng ghi đè lên nó (vô tình hoặc ác ý). Các thẻ, như cơ sở hạ tầng và triển khai của bạn, phải là bất biến .

Bất kể bạn xử lý hình ảnh nội bộ của mình như thế nào, bạn không bao giờ nên sử dụng latestthẻ cho hình ảnh cơ sở vì bạn có thể vô tình triển khai một phiên bản mới với những thay đổi đột ngột đối với quá trình sản xuất.

Đối với hình ảnh nội bộ, hãy sử dụng thẻ mô tả để giúp dễ dàng biết phiên bản mã nào đang chạy, xử lý các lần khôi phục và tránh va chạm đặt tên.

Ví dụ: bạn có thể sử dụng các bộ mô tả sau để tạo thẻ:

  1. Dấu thời gian
  2. ID hình ảnh Docker
  3. Git cam kết băm
  4. Phiên bản ngữ nghĩa

Để có thêm các tùy chọn, hãy xem câu trả lời này từ câu hỏi "Hình ảnh Docker được tạo phiên bản đúng" "Stack Overflow câu hỏi.

Ví dụ:

docker build -t web-prod-a072c4e5d94b5a769225f621f08af3d4bf820a07-0.1.4 .

Ở đây, chúng tôi đã sử dụng những thứ sau để tạo thẻ:

  1. Tên dự án:web
  2. Tên môi trường:prod
  3. Git commit hash:a072c4e5d94b5a769225f621f08af3d4bf820a07
  4. Phiên bản ngữ nghĩa:0.1.4

Điều cần thiết là phải chọn một sơ đồ gắn thẻ và nhất quán với nó. Vì các hàm băm cam kết giúp dễ dàng gắn thẻ hình ảnh trở lại mã một cách nhanh chóng, bạn nên đưa chúng vào sơ đồ gắn thẻ của mình.

Không lưu trữ bí mật trong hình ảnh

Secrets are sensitive pieces of information such as passwords, database credentials, SSH keys, tokens, and TLS certificates, to name a few. These should not be baked into your images without being encrypted since unauthorized users who gain access to the image can merely examine the layers to extract the secrets.

Do not add secrets to your Dockerfiles in plaintext, especially if you're pushing the images to a public registry like Docker Hub:

FROM python:3.9-slim

ENV DATABASE_PASSWORD "SuperSecretSauce"

Instead, they should be injected via:

  1. Environment variables (at run-time)
  2. Build-time arguments (at build-time)
  3. An orchestration tool like Docker Swarm (via Docker secrets) or Kubernetes (via Kubernetes secrets)

Also, you can help prevent leaking secrets by adding common secret files and folders to your .dockerignore file:

**/.env
**/.aws
**/.ssh

Cuối cùng, hãy rõ ràng về những tệp nào đang được sao chép vào hình ảnh thay vì sao chép tất cả các tệp một cách đệ quy:

# BAD
COPY . .

# GOOD
copy ./app.py .

Rõ ràng cũng giúp hạn chế chặn bộ nhớ cache.

Các biến môi trường

Bạn có thể chuyển các bí mật thông qua các biến môi trường, nhưng chúng sẽ hiển thị trong tất cả các quy trình con, vùng chứa được liên kết và nhật ký cũng như thông qua docker inspect. Việc cập nhật chúng cũng rất khó khăn.

$ docker run --detach --env "DATABASE_PASSWORD=SuperSecretSauce" python:3.9-slim

d92cf5cf870eb0fdbf03c666e7fcf18f9664314b79ad58bc7618ea3445e39239


$ docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' d92cf5cf870eb0fdbf03c666e7fcf18f9664314b79ad58bc7618ea3445e39239

DATABASE_PASSWORD=SuperSecretSauce
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=C.UTF-8
GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568
PYTHON_VERSION=3.9.7
PYTHON_PIP_VERSION=21.2.4
PYTHON_SETUPTOOLS_VERSION=57.5.0
PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/c20b0cfd643cd4a19246ccf204e2997af70f6b21/public/get-pip.py
PYTHON_GET_PIP_SHA256=fa6f3fb93cce234cd4e8dd2beb54a51ab9c247653b52855a48dd44e6b21ff28b

Đây là cách tiếp cận đơn giản nhất để quản lý bí mật. Mặc dù nó không phải là cách an toàn nhất, nhưng nó sẽ giữ cho những người lương thiện trung thực vì nó cung cấp một lớp bảo vệ mỏng, giúp giữ bí mật bị che giấu khỏi những cặp mắt lang thang tò mò.

Passing secrets in using a shared volume is a better solution, but they should be encrypted, via Vault or AWS Key Management Service (KMS), since they are saved to disc.

Build-time Arguments

You can pass secrets in at build-time using build-time arguments, but they will be visible to those who have access to the image via docker history.

Example:

FROM python:3.9-slim

ARG DATABASE_PASSWORD

Build:

$ docker build --build-arg "DATABASE_PASSWORD=SuperSecretSauce" .

If you only need to use the secrets temporarily as part of the build -- i.e., SSH keys for cloning a private repo or downloading a private package -- you should use a multi-stage build since the builder history is ignored for temporary stages:

# temp stage
FROM python:3.9-slim as builder

# secret
ARG SSH_PRIVATE_KEY

# install git
RUN apt-get update && \
    apt-get install -y --no-install-recommends git

# use ssh key to clone repo
RUN mkdir -p /root/.ssh/ && \
    echo "${PRIVATE_SSH_KEY}" > /root/.ssh/id_rsa
RUN touch /root/.ssh/known_hosts &&
    ssh-keyscan bitbucket.org >> /root/.ssh/known_hosts
RUN git clone git@github.com:testdrivenio/not-real.git


# final stage
FROM python:3.9-slim

WORKDIR /app

# copy the repository from the temp image
COPY --from=builder /your-repo /app/your-repo

# use the repo for something!

Bản dựng nhiều giai đoạn chỉ giữ lại lịch sử cho hình ảnh cuối cùng. Hãy nhớ rằng bạn có thể sử dụng chức năng này cho các bí mật vĩnh viễn mà bạn cần cho ứng dụng của mình, chẳng hạn như thông tin xác thực cơ sở dữ liệu.

Bạn cũng có thể sử dụng --secrettùy chọn mới trong bản dựng Docker để chuyển các bí mật đến các hình ảnh Docker không được lưu trữ trong hình ảnh.

# "docker_is_awesome" > secrets.txt

FROM alpine

# shows secret from default secret location:
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret

Điều này sẽ gắn kết bí mật từ secrets.txttệp.

Xây dựng hình ảnh:

docker build --no-cache --progress=plain --secret id=mysecret,src=secrets.txt .

# output
...
#4 [1/2] FROM docker.io/library/alpine
#4 sha256:665ba8b2cdc0cb0200e2a42a6b3c0f8f684089f4cd1b81494fbb9805879120f7
#4 CACHED

#5 [2/2] RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
#5 sha256:75601a522ebe80ada66dedd9dd86772ca932d30d7e1b11bba94c04aa55c237de
#5 0.635 docker_is_awesome#5 DONE 0.7s

#6 exporting to image

Cuối cùng, hãy kiểm tra lịch sử để xem liệu bí mật có bị rò rỉ hay không:

❯ docker history 49574a19241c
IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
49574a19241c   5 minutes ago   CMD ["/bin/sh"]                                 0B        buildkit.dockerfile.v0
<missing>      5 minutes ago   RUN /bin/sh -c cat /run/secrets/mysecret # b…   0B        buildkit.dockerfile.v0
<missing>      4 weeks ago     /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>      4 weeks ago     /bin/sh -c #(nop) ADD file:aad4290d27580cc1a…   5.6MB

Để biết thêm về các bí mật về thời gian xây dựng, hãy xem lại Không làm rò rỉ các bí mật về xây dựng hình ảnh Docker của bạn .

Bí mật Docker

Nếu đang sử dụng Docker Swarm , bạn có thể quản lý các bí mật bằng các bí mật Docker .

Ví dụ, init Docker Swarm mode:

$ docker swarm init

Tạo bí mật về docker:

$ echo "supersecretpassword" | docker secret create postgres_password -
qdqmbpizeef0lfhyttxqfbty0

$ docker secret ls
ID                          NAME                DRIVER    CREATED         UPDATED
qdqmbpizeef0lfhyttxqfbty0   postgres_password             4 seconds ago   4 seconds ago

Khi một vùng chứa được cấp quyền truy cập vào bí mật trên, nó sẽ gắn kết tại /run/secrets/postgres_password. Tệp này sẽ chứa giá trị thực của bí mật trong bản rõ.

Sử dụng một công cụ phân tích khác nhau?

  1. AWS EKS - Sử dụng các bí mật của AWS Secrets Manager với Kubernetes
  2. DigitalOcean Kubernetes - Các bước được đề xuất để bảo mật một Cụm Kubernetes DigitalOcean
  3. Google Kubernetes Engine - Sử dụng Trình quản lý bí mật với các sản phẩm khác
  4. Nomad - Tích hợp Vault và Truy xuất Bí mật Động

Sử dụng tệp .dockerignore

Chúng tôi đã đề cập đến việc sử dụng tệp .dockerignore một vài lần rồi. Tệp này được sử dụng để chỉ định các tệp và thư mục mà bạn không muốn thêm vào ngữ cảnh xây dựng ban đầu được gửi đến trình nền Docker, sau đó sẽ xây dựng hình ảnh của bạn. Nói cách khác, bạn có thể sử dụng nó để xác định bối cảnh xây dựng mà bạn cần.

Khi hình ảnh Docker được tạo, toàn bộ ngữ cảnh Docker - tức là gốc của dự án của bạn - sẽ được gửi đến trình nền Docker trước khi các lệnh COPYhoặc ADDđược đánh giá. Điều này có thể khá tốn kém, đặc biệt nếu bạn có nhiều phụ thuộc, tệp dữ liệu lớn hoặc tạo tác phẩm xây dựng trong dự án của mình. Ngoài ra, Docker CLI và daemon có thể không nằm trên cùng một máy. Vì vậy, nếu daemon được thực thi trên một máy tính từ xa, bạn nên chú ý hơn nữa đến kích thước của ngữ cảnh xây dựng.

Bạn nên thêm gì vào tệp .dockerignore ?

  1. Tệp và thư mục tạm thời
  2. Xây dựng nhật ký
  3. Bí mật địa phương
  4. Các tệp phát triển cục bộ như docker-compost.yml
  5. Các thư mục kiểm soát phiên bản như ".git", ".hg" và ".svn"

Thí dụ:

**/.git
**/.gitignore
**/.vscode
**/coverage
**/.env
**/.aws
**/.ssh
Dockerfile
README.md
docker-compose.yml
**/.DS_Store
**/venv
**/env

Tóm lại, .dockerignore có cấu trúc đúng có thể giúp:

  1. Giảm kích thước của hình ảnh Docker
  2. Tăng tốc quá trình xây dựng
  3. Ngăn chặn việc vô hiệu hóa bộ nhớ cache không cần thiết
  4. Ngăn chặn rò rỉ bí mật

Lint và quét Dockerfiles và hình ảnh của bạn

Linting là quá trình kiểm tra mã nguồn của bạn để tìm các lỗi lập trình và văn phong cũng như các hoạt động không tốt có thể dẫn đến các sai sót tiềm ẩn. Cũng giống như các ngôn ngữ lập trình, các tệp tĩnh cũng có thể được in ra. Đặc biệt với Dockerfiles của bạn, linters có thể giúp đảm bảo chúng có thể bảo trì được, tránh cú pháp không dùng nữa và tuân thủ các phương pháp hay nhất. Linting hình ảnh của bạn phải là một phần tiêu chuẩn của đường ống CI của bạn.

Hadolint là trình liên kết Dockerfile phổ biến nhất:

$ hadolint Dockerfile

Dockerfile:1 DL3006 warning: Always tag the version of an image explicitly
Dockerfile:7 DL3042 warning: Avoid the use of cache directory with pip. Use `pip install --no-cache-dir <package>`
Dockerfile:9 DL3059 info: Multiple consecutive `RUN` instructions. Consider consolidation.
Dockerfile:17 DL3025 warning: Use arguments JSON notation for CMD and ENTRYPOINT arguments

Bạn có thể xem nó hoạt động trực tuyến tại https://hadolint.github.io/hadolint/ . Ngoài ra còn có một phần mở rộng mã VS.

Bạn có thể kết hợp các tệp Dockerfiles của mình bằng cách quét các hình ảnh và vùng chứa để tìm các lỗ hổng.

Một số tùy chọn:

  1. Snyk là nhà cung cấp độc quyền quét lỗ hổng bảo mật cho Docker. Bạn có thể sử dụng docker scanlệnh CLI để quét hình ảnh.
  2. Trivy có thể được sử dụng để quét hình ảnh vùng chứa, hệ thống tệp, kho lưu trữ git và các tệp cấu hình khác.
  3. Clair là một dự án mã nguồn mở được sử dụng để phân tích tĩnh các lỗ hổng trong các vùng chứa ứng dụng.
  4. Anchore là một dự án mã nguồn mở cung cấp dịch vụ tập trung để kiểm tra, phân tích và chứng nhận hình ảnh vùng chứa.

Tóm lại, lint và quét các tệp Dockerfiles và hình ảnh của bạn để xác định bất kỳ vấn đề tiềm ẩn nào khác với các phương pháp hay nhất.

Ký tên và xác minh hình ảnh

Làm thế nào để bạn biết rằng những hình ảnh được sử dụng để chạy mã sản xuất của bạn không bị giả mạo?

Giả mạo có thể xảy ra qua đường dây thông qua các cuộc tấn công man-in-the-middle (MITM) hoặc từ việc đăng ký bị xâm phạm hoàn toàn.

Docker Content Trust (DCT) cho phép ký và xác minh hình ảnh Docker từ các cơ quan đăng ký từ xa.

Để xác minh tính toàn vẹn và tính xác thực của hình ảnh, hãy đặt biến môi trường sau:

DOCKER_CONTENT_TRUST=1

Bây giờ, nếu bạn cố gắng kéo một hình ảnh chưa được ký, bạn sẽ nhận được lỗi sau:

Error: remote trust data does not exist for docker.io/namespace/unsigned-image:
notary.docker.io does not have trust data for docker.io/namespace/unsigned-image

Bạn có thể tìm hiểu về hình ảnh ký từ tài liệu Hình ảnh ký với Docker Content Trust .

Khi tải xuống hình ảnh từ Docker Hub, hãy đảm bảo sử dụng hình ảnh chính thức hoặc hình ảnh đã được xác minh từ các nguồn đáng tin cậy. Các nhóm lớn hơn nên sử dụng sổ đăng ký vùng chứa riêng nội bộ của riêng họ .

Mẹo thưởng

Sử dụng môi trường ảo Python

Bạn có nên sử dụng môi trường ảo bên trong vùng chứa không?

Trong hầu hết các trường hợp, môi trường ảo là không cần thiết miễn là bạn chỉ chạy một quy trình duy nhất cho mỗi vùng chứa. Vì bản thân vùng chứa cung cấp khả năng cách ly, các gói có thể được cài đặt trên toàn hệ thống. Điều đó nói rằng, bạn có thể muốn sử dụng môi trường ảo trong một bản dựng nhiều giai đoạn hơn là xây dựng các tệp bánh xe.

Ví dụ với bánh xe:

# temp stage
FROM python:3.9-slim as builder

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc

COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt


# final stage
FROM python:3.9-slim

WORKDIR /app

COPY --from=builder /app/wheels /wheels
COPY --from=builder /app/requirements.txt .

RUN pip install --no-cache /wheels/*

Ví dụ với virtualenv:

# temp stage
FROM python:3.9-slim as builder

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc

RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

COPY requirements.txt .
RUN pip install -r requirements.txt


# final stage
FROM python:3.9-slim

COPY --from=builder /opt/venv /opt/venv

WORKDIR /app

ENV PATH="/opt/venv/bin:$PATH"

Đặt giới hạn bộ nhớ và CPU

It's a good idea to limit the memory usage of your Docker containers, especially if you're running multiple containers on a single machine. This can prevent any of the containers from using all available memory and thereby crippling the rest.

The easiest way to limit memory usage is to use --memory and --cpu options in the Docker cli:

$ docker run --cpus=2 -m 512m nginx

The above command limits the container usage to 2 CPUs and 512 megabytes of main memory.

You can do the same in a Docker Compose file like so:

version: "3.9"
services:
  redis:
    image: redis:alpine
    deploy:
      resources:
        limits:
          cpus: 2
          memory: 512M
        reservations:
          cpus: 1
          memory: 256M

Take note of the reservations field. It's used to set a soft limit, which takes priority when the host machine has low memory or CPU resources.

Additional resources:

  1. Runtime options with Memory, CPUs, and GPUs
  2. Docker Compose resouce constraints

Log to stdout or stderr

Các ứng dụng chạy trong vùng chứa Docker của bạn nên ghi thông báo nhật ký vào đầu ra tiêu chuẩn (stdout) và lỗi tiêu chuẩn (stderr) chứ không phải vào tệp.

Sau đó, bạn có thể định cấu hình daemon Docker để gửi các thông báo nhật ký của bạn đến một giải pháp ghi nhật ký tập trung (như CloudWatch Logs hoặc Papertrail ).

Để biết thêm, hãy xem Xử lý nhật ký dưới dạng luồng sự kiện từ Ứng dụng Mười hai nhân tố và Định cấu hình trình điều khiển ghi nhật ký từ tài liệu Docker.

Sử dụng một giá đỡ bộ nhớ dùng chung cho Gunicorn Heartbeat

Gunicorn sử dụng một hệ thống nhịp tim dựa trên tệp để đảm bảo rằng tất cả các quy trình nhân viên đã phân tách đều hoạt động.

Trong hầu hết các trường hợp, các tệp nhịp tim được tìm thấy trong "/ tmp", thường nằm trong bộ nhớ thông qua tmpfs . Vì Docker không tận dụng tmpfs theo mặc định, các tệp sẽ được lưu trữ trên hệ thống tệp được sao lưu bằng đĩa. Điều này có thể gây ra sự cố , chẳng hạn như đóng băng ngẫu nhiên do hệ thống nhịp tim sử dụng os.fchmod, điều này có thể chặn nhân viên nếu thực tế thư mục nằm trên hệ thống tệp được hỗ trợ bằng đĩa.

May mắn thay, có một cách khắc phục đơn giản: Thay đổi thư mục nhịp tim thành thư mục được ánh xạ bộ nhớ thông qua --worker-tmp-dircờ.

gunicorn --worker-tmp-dir /dev/shm config.wsgi -b 0.0.0.0:8000

Sự kết luận

Bài viết này xem xét một số phương pháp hay nhất để làm cho Dockerfiles và hình ảnh của bạn sạch hơn, gọn gàng hơn và an toàn hơn.

Nguồn:  https://testdriven.io

#python #docker 

What is GEEK

Buddha Community

Một Số Phương Pháp Hay Nhất Của Docker Dành Cho Nhà Phát Triển Python
Hoang Tran

Hoang Tran

1660555320

Một Số Phương Pháp Hay Nhất Của Docker Dành Cho Nhà Phát Triển Python

Bài viết này xem xét một số phương pháp hay nhất để làm theo khi viết Dockerfiles và làm việc với Docker nói chung. Trong khi hầu hết các phương pháp được liệt kê áp dụng cho tất cả các nhà phát triển, bất kể ngôn ngữ, một số ít áp dụng cho những người đang phát triển các ứng dụng dựa trên Python.

Dockerfiles

Sử dụng Công trình nhiều giai đoạn

Tận dụng các bản dựng nhiều giai đoạn để tạo hình ảnh Docker gọn gàng hơn, an toàn hơn.

Các bản dựng Docker nhiều giai đoạn cho phép bạn chia các Dockerfiles của mình thành nhiều giai đoạn. Ví dụ: bạn có thể có một giai đoạn để biên dịch và xây dựng ứng dụng của mình, giai đoạn này sau đó có thể được sao chép sang các giai đoạn tiếp theo. Vì chỉ giai đoạn cuối cùng được sử dụng để tạo hình ảnh, các phần phụ thuộc và công cụ liên quan đến việc xây dựng ứng dụng của bạn sẽ bị loại bỏ, để lại hình ảnh sẵn sàng sản xuất theo mô-đun và gọn gàng.

Ví dụ về phát triển web:

# temp stage
FROM python:3.9-slim as builder

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc

COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt


# final stage
FROM python:3.9-slim

WORKDIR /app

COPY --from=builder /app/wheels /wheels
COPY --from=builder /app/requirements.txt .

RUN pip install --no-cache /wheels/*

Trong ví dụ này, trình biên dịch GCC được yêu cầu để cài đặt các gói Python nhất định, vì vậy chúng tôi đã thêm một giai đoạn tạm thời, thời gian xây dựng để xử lý giai đoạn xây dựng. Vì hình ảnh thời gian chạy cuối cùng không chứa GCC nên nó nhẹ hơn và an toàn hơn nhiều.

So sánh kích thước:

REPOSITORY                 TAG                    IMAGE ID       CREATED          SIZE
docker-single              latest                 8d6b6a4d7fb6   16 seconds ago   259MB
docker-multi               latest                 813c2fa9b114   3 minutes ago    156MB

Ví dụ về khoa học dữ liệu:

# temp stage
FROM python:3.9 as builder

RUN pip wheel --no-cache-dir --no-deps --wheel-dir /wheels jupyter pandas


# final stage
FROM python:3.9-slim

WORKDIR /notebooks

COPY --from=builder /wheels /wheels
RUN pip install --no-cache /wheels/*

So sánh kích thước:

REPOSITORY                  TAG                   IMAGE ID       CREATED         SIZE
ds-multi                    latest                b4195deac742   2 minutes ago   357MB
ds-single                   latest                7c23c43aeda6   6 minutes ago   969MB

In summary, multi-stage builds can decrease the size of your production images, helping you save time and money. In addition, this will simplify your production containers. Also, due to the smaller size and simplicity, there's potentially a smaller attack surface.

Order Dockerfile Commands Appropriately

Pay close attention to the order of your Dockerfile commands to leverage layer caching.

Docker caches each step (or layer) in a particular Dockerfile to speed up subsequent builds. When a step changes, the cache will be invalidated not only for that particular step but all succeeding steps.

Example:

FROM python:3.9-slim

WORKDIR /app

COPY sample.py .

COPY requirements.txt .

RUN pip install -r /requirements.txt

In this Dockerfile, we copied over the application code before installing the requirements. Now, each time we change sample.py, the build will reinstall the packages. This is very inefficient, especially when using a Docker container as a development environment. Therefore, it's crucial to keep the files that frequently change towards the end of the Dockerfile.

You can also help prevent unwanted cache invalidations by using a .dockerignore file to exclude unnecessary files from being added to the Docker build context and the final image. More on this here shortly.

So, in the above Dockerfile, you should move the COPY sample.py . command to the bottom:

FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install -r /requirements.txt

COPY sample.py .

Notes:

  1. Always put layers that are likely to change as low as possible in the Dockerfile.
  2. Combine RUN apt-get update and RUN apt-get install commands. (This also helps to reduce the image size. We'll touch on this shortly.)
  3. If you want to turn off caching for a particular Docker build, add the --no-cache=True flag.

Use Small Docker Base Images

Smaller Docker images are more modular and secure.

Building, pushing, and pulling images is quicker with smaller images. They also tend to be more secure since they only include the necessary libraries and system dependencies required for running your application.

Which Docker base image should you use?

Unfortunately, it depends.

Here's a size comparison of various Docker base images for Python:

REPOSITORY   TAG                 IMAGE ID       CREATED      SIZE
python       3.9.6-alpine3.14    f773016f760e   3 days ago   45.1MB
python       3.9.6-slim          907fc13ca8e7   3 days ago   115MB
python       3.9.6-slim-buster   907fc13ca8e7   3 days ago   115MB
python       3.9.6               cba42c28d9b8   3 days ago   886MB
python       3.9.6-buster        cba42c28d9b8   3 days ago   886MB

Mặc dù phiên bản Alpine, dựa trên Alpine Linux , là phiên bản nhỏ nhất, nhưng nó thường có thể dẫn đến việc tăng thời gian xây dựng nếu bạn không thể tìm thấy các tệp nhị phân đã biên dịch hoạt động với nó. Do đó, bạn có thể phải tự tạo các tệp nhị phân, điều này có thể làm tăng kích thước hình ảnh (tùy thuộc vào các phụ thuộc cấp hệ thống được yêu cầu) và thời gian xây dựng (do phải biên dịch từ nguồn).

Tham khảo Hình ảnh cơ sở Docker tốt nhất cho ứng dụng Python của bạnSử dụng Alpine có thể làm cho các bản dựng Docker Python chậm hơn 50 lần để biết thêm lý do tại sao tốt nhất nên tránh sử dụng hình ảnh cơ sở dựa trên Alpine.

Cuối cùng, tất cả là về sự cân bằng. Khi nghi ngờ, hãy bắt đầu với một *-slimhương vị, đặc biệt là trong chế độ phát triển, khi bạn đang xây dựng ứng dụng của mình. Bạn muốn tránh phải liên tục cập nhật Dockerfile để cài đặt các phụ thuộc cấp hệ thống cần thiết khi bạn thêm một gói Python mới. Khi bạn làm cứng ứng dụng của mình và (các) Dockerfile để sản xuất, bạn có thể muốn khám phá bằng cách sử dụng Alpine cho hình ảnh cuối cùng từ một bản dựng nhiều giai đoạn.

Ngoài ra, đừng quên cập nhật hình ảnh cơ sở của bạn thường xuyên để cải thiện bảo mật và tăng hiệu suất. Khi phiên bản mới của hình ảnh cơ sở được phát hành - tức là 3.9.6-slim-> 3.9.7-slim- bạn nên kéo hình ảnh mới và cập nhật các vùng chứa đang chạy của mình để nhận tất cả các bản vá bảo mật mới nhất.

Giảm thiểu số lớp

Bạn nên kết hợp các RUNlệnh COPYADDcàng nhiều càng tốt vì chúng tạo ra các lớp. Mỗi lớp sẽ tăng kích thước của hình ảnh vì chúng được lưu vào bộ nhớ đệm. Do đó, khi số lượng lớp tăng lên, kích thước cũng tăng lên.

Bạn có thể kiểm tra điều này bằng docker historylệnh:

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
dockerfile   latest    180f98132d02   51 seconds ago   259MB

$ docker history 180f98132d02

IMAGE          CREATED              CREATED BY                                      SIZE      COMMENT
180f98132d02   58 seconds ago       COPY . . # buildkit                             6.71kB    buildkit.dockerfile.v0
<missing>      58 seconds ago       RUN /bin/sh -c pip install -r requirements.t…   35.5MB    buildkit.dockerfile.v0
<missing>      About a minute ago   COPY requirements.txt . # buildkit              58B       buildkit.dockerfile.v0
<missing>      About a minute ago   WORKDIR /app
...

Hãy lưu ý các kích thước. Chỉ các lệnh RUN, COPYvà mới ADDthêm kích thước vào hình ảnh. Bạn có thể giảm kích thước hình ảnh bằng cách kết hợp các lệnh nếu có thể. Ví dụ:

RUN apt-get update
RUN apt-get install -y netcat

Có thể được kết hợp thành một RUNlệnh duy nhất:

RUN apt-get update && apt-get install -y netcat

Do đó, việc tạo một lớp duy nhất thay vì hai, làm giảm kích thước của hình ảnh cuối cùng.

Mặc dù giảm số lượng lớp là một ý tưởng hay, nhưng điều đó quan trọng hơn nhiều để điều đó không phải là mục tiêu và tác dụng phụ của việc giảm kích thước hình ảnh và thời gian xây dựng. Nói cách khác, hãy tập trung nhiều hơn vào ba phương pháp trước đó - xây dựng nhiều giai đoạn, thứ tự các lệnh Dockerfile của bạn và sử dụng một hình ảnh cơ sở nhỏ - hơn là cố gắng tối ưu hóa từng lệnh đơn lẻ.

Ghi chú:

  1. RUN, COPYADDmỗi lớp tạo ra.
  2. Mỗi lớp chứa những điểm khác biệt so với lớp trước đó.
  3. Các lớp làm tăng kích thước của hình ảnh cuối cùng.

Lời khuyên:

  1. Kết hợp các lệnh liên quan.
  2. Loại bỏ các tệp không cần thiết trong cùng một RUN stepđã tạo chúng.
  3. Giảm thiểu số lần apt-get upgradechạy vì nó nâng cấp tất cả các gói lên phiên bản mới nhất.
  4. Với các bản dựng nhiều giai đoạn, đừng quá lo lắng về việc tối ưu hóa quá mức các lệnh trong các giai đoạn tạm thời.

Cuối cùng, để dễ đọc, bạn nên sắp xếp các đối số nhiều dòng theo kiểu chữ và số:

RUN apt-get update && apt-get install -y \
    git \
    gcc \
    matplotlib \
    pillow  \
    && rm -rf /var/lib/apt/lists/*

Sử dụng vùng chứa không đặc quyền

Theo mặc định, Docker chạy các quy trình vùng chứa dưới dạng root bên trong vùng chứa. Tuy nhiên, đây là một thực tiễn không tốt vì quá trình chạy dưới quyền root bên trong vùng chứa đang chạy dưới quyền root trong máy chủ Docker. Do đó, nếu kẻ tấn công giành được quyền truy cập vào vùng chứa của bạn, chúng có quyền truy cập vào tất cả các đặc quyền gốc và có thể thực hiện một số cuộc tấn công chống lại máy chủ Docker, như-

  1. sao chép thông tin nhạy cảm từ hệ thống tệp của máy chủ lưu trữ vào vùng chứa
  2. thực hiện các lệnh từ xa

Để ngăn chặn điều này, hãy đảm bảo chạy các quy trình vùng chứa với người dùng không phải root:

RUN addgroup --system app && adduser --system --group app

USER app

Bạn có thể thực hiện thêm một bước nữa và xóa quyền truy cập shell và đảm bảo rằng không có thư mục chính:

RUN addgroup --gid 1001 --system app && \
    adduser --no-create-home --shell /bin/false --disabled-password --uid 1001 --system --group app

USER app

Kiểm chứng:

$ docker run -i sample id

uid=1001(app) gid=1001(app) groups=1001(app)

Ở đây, ứng dụng trong vùng chứa chạy dưới quyền người dùng không phải root. Tuy nhiên, hãy nhớ rằng daemon Docker và bản thân vùng chứa vẫn đang chạy với đặc quyền root. Đảm bảo xem lại Chạy trình nền Docker với tư cách người dùng không phải root để được trợ giúp chạy cả daemon và vùng chứa với tư cách người dùng không phải root.

Thích COPY hơn ADD

Sử dụng COPYtrừ khi bạn chắc chắn mình cần chức năng bổ sung đi kèm ADD.

Sự khác biệt giữa COPYADDlà gì?

Cả hai lệnh đều cho phép bạn sao chép tệp từ một vị trí cụ thể vào hình ảnh Docker:

ADD <src> <dest>
COPY <src> <dest>

Mặc dù chúng trông giống như phục vụ cùng một mục đích, nhưng ADDcó một số chức năng bổ sung:

  • COPYđược sử dụng để sao chép các tệp hoặc thư mục cục bộ từ máy chủ Docker sang hình ảnh.
  • ADDcó thể được sử dụng cho cùng một thứ cũng như tải xuống các tệp bên ngoài. Ngoài ra, nếu bạn sử dụng tệp nén (tar, gzip, bzip2, v.v.) làm <src>tham số, ADDsẽ tự động giải nén nội dung vào vị trí nhất định.
# copy local files on the host  to the destination
COPY /source/path  /destination/path
ADD /source/path  /destination/path

# download external file and copy to the destination
ADD http://external.file/url  /destination/path

# copy and extract local compresses files
ADD source.file.tar.gz /destination/path

Cache các gói Python vào Máy chủ Docker

Khi tệp yêu cầu được thay đổi, hình ảnh cần được xây dựng lại để cài đặt các gói mới. Các bước trước đó sẽ được lưu vào bộ nhớ đệm, như đã đề cập trong Giảm thiểu số lớp . Tải xuống tất cả các gói trong khi xây dựng lại hình ảnh có thể gây ra nhiều hoạt động mạng và mất nhiều thời gian. Mỗi bản xây dựng lại chiếm cùng một lượng thời gian để tải xuống các gói chung trên các bản dựng.

Bạn có thể tránh điều này bằng cách ánh xạ thư mục bộ đệm pip vào một thư mục trên máy chủ. Vì vậy, đối với mỗi lần xây dựng lại, các phiên bản đã lưu trong bộ nhớ cache vẫn tồn tại và có thể cải thiện tốc độ xây dựng.

Thêm một ổ đĩa để chạy docker dưới dạng -v $HOME/.cache/pip-docker/:/root/.cache/piphoặc dưới dạng ánh xạ trong tệp Docker Compose.

Thư mục được trình bày ở trên chỉ mang tính chất tham khảo. Đảm bảo rằng bạn ánh xạ thư mục bộ nhớ cache chứ không phải các gói trang (nơi chứa các gói đã xây dựng).

Di chuyển bộ nhớ cache từ hình ảnh docker sang máy chủ lưu trữ có thể giúp bạn tiết kiệm dung lượng trong hình ảnh cuối cùng.

Nếu bạn đang tận dụng Docker BuildKit , hãy sử dụng gắn kết bộ đệm BuildKit để quản lý bộ đệm:

# syntax = docker/dockerfile:1.2

...

COPY requirements.txt .

RUN --mount=type=cache,target=/root/.cache/pip \
        pip install -r requirements.txt

...

Chỉ chạy một quy trình cho mỗi vùng chứa

Tại sao chỉ nên chạy một quy trình cho mỗi vùng chứa?

Giả sử ngăn xếp ứng dụng của bạn bao gồm hai máy chủ web và một cơ sở dữ liệu. Mặc dù bạn có thể dễ dàng chạy cả ba từ một vùng chứa duy nhất, nhưng bạn nên chạy từng vùng trong một vùng chứa riêng biệt để dễ dàng sử dụng lại và mở rộng quy mô từng dịch vụ riêng lẻ.

  1. Chia tỷ lệ - Với mỗi dịch vụ trong một vùng chứa riêng biệt, bạn có thể chia tỷ lệ một trong các máy chủ web của mình theo chiều ngang khi cần để xử lý nhiều lưu lượng truy cập hơn.
  2. Khả năng tái sử dụng - Có lẽ bạn có một dịch vụ khác cần một cơ sở dữ liệu được lưu trữ. Bạn có thể đơn giản sử dụng lại cùng một vùng chứa cơ sở dữ liệu mà không cần mang theo hai dịch vụ không cần thiết cùng với nó.
  3. Ghi nhật ký - Các thùng chứa ghép nối làm cho việc ghi nhật ký trở nên phức tạp hơn nhiều. Chúng tôi sẽ giải quyết vấn đề này một cách chi tiết hơn ở phần sau của bài viết này.
  4. Tính di động và khả năng dự đoán - Việc tạo các bản vá bảo mật hoặc gỡ lỗi sự cố dễ dàng hơn nhiều khi có ít diện tích bề mặt để làm việc hơn.

Ưu tiên mảng hơn cú pháp chuỗi

Bạn có thể viết lệnh CMDENTRYPOINTtrong Dockerfiles của mình ở cả định dạng mảng (thi hành) hoặc chuỗi (trình bao):

# array (exec)
CMD ["gunicorn", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "main:app"]

# string (shell)
CMD "gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app"

Cả hai đều đúng và đạt được điều gần như giống nhau; tuy nhiên, bạn nên sử dụng định dạng thực thi bất cứ khi nào có thể. Từ tài liệu Docker :

  1. Đảm bảo rằng bạn đang sử dụng biểu mẫu thực thi của CMDENTRYPOINTtrong Dockerfile của mình.
  2. Ví dụ sử dụng ["program", "arg1", "arg2"]not "program arg1 arg2". Việc sử dụng biểu mẫu chuỗi khiến Docker chạy quy trình của bạn bằng cách sử dụng bash, quy trình này không xử lý các tín hiệu đúng cách. Soạn luôn sử dụng biểu mẫu JSON, vì vậy đừng lo lắng nếu bạn ghi đè lệnh hoặc điểm nhập trong tệp Soạn của mình.

Vì vậy, vì hầu hết các trình bao không xử lý tín hiệu cho các quy trình con, nếu bạn sử dụng định dạng trình bao, CTRL-C(tạo ra a SIGTERM) có thể không dừng một quy trình con.

Thí dụ:

FROM ubuntu:18.04

# BAD: shell format
ENTRYPOINT top -d

# GOOD: exec format
ENTRYPOINT ["top", "-d"]

Hãy thử cả hai. Lưu ý rằng với hương vị định dạng shell, CTRL-Csẽ không giết chết quá trình. Thay vào đó, bạn sẽ thấy ^C^C^C^C^C^C^C^C^C^C^C.

Một lưu ý khác là định dạng shell mang PID của shell, không phải của chính quá trình.

# array format
root@18d8fd3fd4d2:/app# ps ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 python manage.py runserver 0.0.0.0:8000
    7 ?        Sl     0:02 /usr/local/bin/python manage.py runserver 0.0.0.0:8000
   25 pts/0    Ss     0:00 bash
  356 pts/0    R+     0:00 ps ax


# string format
root@ede24a5ef536:/app# ps ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 /bin/sh -c python manage.py runserver 0.0.0.0:8000
    8 ?        S      0:00 python manage.py runserver 0.0.0.0:8000
    9 ?        Sl     0:01 /usr/local/bin/python manage.py runserver 0.0.0.0:8000
   13 pts/0    Ss     0:00 bash
  342 pts/0    R+     0:00 ps ax

Hiểu sự khác biệt giữa ENTRYPOINT và CMD

Tôi có nên sử dụng ENTRYPOINT hoặc CMD để chạy các quy trình vùng chứa không?

Có hai cách để chạy lệnh trong vùng chứa:

CMD ["gunicorn", "config.wsgi", "-b", "0.0.0.0:8000"]

# and

ENTRYPOINT ["gunicorn", "config.wsgi", "-b", "0.0.0.0:8000"]

Về cơ bản, cả hai đều làm cùng một việc: Khởi động ứng dụng tại config.wsgimáy chủ Gunicorn và liên kết nó với 0.0.0.0:8000.

CMDDễ dàng bị ghi đè . Nếu bạn chạy docker run <image_name> uvicorn config.asgi, CMD ở trên sẽ được thay thế bằng các đối số mới - ví dụ uvicorn config.asgi:. Trong khi để ghi đè ENTRYPOINTlệnh, người ta phải chỉ định --entrypointtùy chọn:

docker run --entrypoint uvicorn config.asgi <image_name>

Ở đây, rõ ràng là chúng ta đang ghi đè điểm vào. Vì vậy, bạn nên sử dụng ENTRYPOINThết CMDđể tránh vô tình ghi đè lệnh.

Chúng cũng có thể được sử dụng cùng nhau.

Ví dụ:

ENTRYPOINT ["gunicorn", "config.wsgi", "-w"]
CMD ["4"]

Khi được sử dụng cùng nhau như vậy, lệnh được chạy để khởi động vùng chứa là:

gunicorn config.wsgi -w 4

As discussed above, CMD is easily overridden. Thus, CMD can be used to pass arguments to the ENTRYPOINT command. The number of workers can be easily changed like so:

docker run <image_name> 6

This will start the container with six Gunicorn workers rather then four.

Include a HEALTHCHECK Instruction

Use a HEALTHCHECK to determine if the process running in the container is not only up and running, but is "healthy" as well.

Docker tiết lộ một API để kiểm tra trạng thái của quá trình đang chạy trong vùng chứa, cung cấp nhiều thông tin hơn là chỉ xem liệu quá trình có "đang chạy" hay không vì "đang chạy" bao gồm "nó đã hoạt động và hoạt động", "vẫn đang chạy", và thậm chí "bị mắc kẹt trong một số trạng thái lỗi vòng lặp vô hạn". Bạn có thể tương tác với API này thông qua hướng dẫn HEALTHCHECK .

Ví dụ: nếu bạn đang cung cấp một ứng dụng web, bạn có thể sử dụng các thông số sau để xác định xem /điểm cuối có hoạt động hay không và có thể xử lý các yêu cầu cung cấp:

HEALTHCHECK CMD curl --fail http://localhost:8000 || exit 1

Nếu bạn chạy docker ps, bạn có thể thấy trạng thái của HEALTHCHECK.

Ví dụ lành mạnh:

CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS                            PORTS                                       NAMES
09c2eb4970d4   healthcheck   "python manage.py ru…"   10 seconds ago   Up 8 seconds (health: starting)   0.0.0.0:8000->8000/tcp, :::8000->8000/tcp   xenodochial_clarke

Ví dụ không lành mạnh:

CONTAINER ID   IMAGE         COMMAND                  CREATED              STATUS                          PORTS                                       NAMES
09c2eb4970d4   healthcheck   "python manage.py ru…"   About a minute ago   Up About a minute (unhealthy)   0.0.0.0:8000->8000/tcp, :::8000->8000/tcp   xenodochial_clarke

Bạn có thể thực hiện thêm một bước và thiết lập một điểm cuối tùy chỉnh chỉ được sử dụng để kiểm tra tình trạng và sau đó định cấu hình HEALTHCHECKđể kiểm tra dựa trên dữ liệu trả về. Ví dụ: nếu điểm cuối trả về phản hồi JSON {"ping": "pong"}, bạn có thể hướng dẫn HEALTHCHECKxác thực phần thân phản hồi.

Đây là cách bạn xem trạng thái tình trạng kiểm tra sức khỏe bằng cách sử dụng docker inspect:

❯ docker inspect --format "{{json .State.Health }}" ab94f2ac7889
{
  "Status": "healthy",
  "FailingStreak": 0,
  "Log": [
    {
      "Start": "2021-09-28T15:22:57.5764644Z",
      "End": "2021-09-28T15:22:57.7825527Z",
      "ExitCode": 0,
      "Output": "..."

Ở đây, đầu ra được cắt bớt vì nó chứa toàn bộ đầu ra HTML.

Bạn cũng có thể thêm kiểm tra sức khỏe vào tệp Docker Compose:

version: "3.8"

services:
  web:
    build: .
    ports:
      - '8000:8000'
    healthcheck:
      test: curl --fail http://localhost:8000 || exit 1
      interval: 10s
      timeout: 10s
      start_period: 10s
      retries: 3

Tùy chọn:

  • test: Lệnh kiểm tra.
  • interval: Khoảng thời gian cần kiểm tra - tức là kiểm tra mọi xđơn vị thời gian.
  • timeout: Thời gian tối đa để chờ phản hồi.
  • start_period: Khi nào thì bắt đầu kiểm tra sức khỏe. Nó có thể được sử dụng khi các tác vụ bổ sung được thực hiện trước khi các vùng chứa sẵn sàng, chẳng hạn như chạy quá trình di chuyển.
  • retries: Thử lại tối đa trước khi chỉ định thử nghiệm là failed.

Nếu bạn đang sử dụng một công cụ điều phối không phải Docker Swarm - tức là Kubernetes hoặc AWS ECS - thì rất có thể công cụ này có hệ thống nội bộ riêng để xử lý kiểm tra sức khỏe. Tham khảo tài liệu của công cụ cụ thể trước khi thêm HEALTHCHECKhướng dẫn.

Hình ảnh

Hình ảnh phiên bản Docker

Bất cứ khi nào có thể, hãy tránh sử dụng latestthẻ.

Nếu bạn dựa vào latestthẻ (thực sự không phải là "thẻ" vì nó được áp dụng theo mặc định khi hình ảnh không được gắn thẻ rõ ràng), bạn không thể biết phiên bản mã nào của mình đang chạy dựa trên thẻ hình ảnh. Nó làm cho việc quay ngược trở lại khó khăn và dễ dàng ghi đè lên nó (vô tình hoặc ác ý). Các thẻ, như cơ sở hạ tầng và triển khai của bạn, phải là bất biến .

Bất kể bạn xử lý hình ảnh nội bộ của mình như thế nào, bạn không bao giờ nên sử dụng latestthẻ cho hình ảnh cơ sở vì bạn có thể vô tình triển khai một phiên bản mới với những thay đổi đột ngột đối với quá trình sản xuất.

Đối với hình ảnh nội bộ, hãy sử dụng thẻ mô tả để giúp dễ dàng biết phiên bản mã nào đang chạy, xử lý các lần khôi phục và tránh va chạm đặt tên.

Ví dụ: bạn có thể sử dụng các bộ mô tả sau để tạo thẻ:

  1. Dấu thời gian
  2. ID hình ảnh Docker
  3. Git cam kết băm
  4. Phiên bản ngữ nghĩa

Để có thêm các tùy chọn, hãy xem câu trả lời này từ câu hỏi "Hình ảnh Docker được tạo phiên bản đúng" "Stack Overflow câu hỏi.

Ví dụ:

docker build -t web-prod-a072c4e5d94b5a769225f621f08af3d4bf820a07-0.1.4 .

Ở đây, chúng tôi đã sử dụng những thứ sau để tạo thẻ:

  1. Tên dự án:web
  2. Tên môi trường:prod
  3. Git commit hash:a072c4e5d94b5a769225f621f08af3d4bf820a07
  4. Phiên bản ngữ nghĩa:0.1.4

Điều cần thiết là phải chọn một sơ đồ gắn thẻ và nhất quán với nó. Vì các hàm băm cam kết giúp dễ dàng gắn thẻ hình ảnh trở lại mã một cách nhanh chóng, bạn nên đưa chúng vào sơ đồ gắn thẻ của mình.

Không lưu trữ bí mật trong hình ảnh

Secrets are sensitive pieces of information such as passwords, database credentials, SSH keys, tokens, and TLS certificates, to name a few. These should not be baked into your images without being encrypted since unauthorized users who gain access to the image can merely examine the layers to extract the secrets.

Do not add secrets to your Dockerfiles in plaintext, especially if you're pushing the images to a public registry like Docker Hub:

FROM python:3.9-slim

ENV DATABASE_PASSWORD "SuperSecretSauce"

Instead, they should be injected via:

  1. Environment variables (at run-time)
  2. Build-time arguments (at build-time)
  3. An orchestration tool like Docker Swarm (via Docker secrets) or Kubernetes (via Kubernetes secrets)

Also, you can help prevent leaking secrets by adding common secret files and folders to your .dockerignore file:

**/.env
**/.aws
**/.ssh

Cuối cùng, hãy rõ ràng về những tệp nào đang được sao chép vào hình ảnh thay vì sao chép tất cả các tệp một cách đệ quy:

# BAD
COPY . .

# GOOD
copy ./app.py .

Rõ ràng cũng giúp hạn chế chặn bộ nhớ cache.

Các biến môi trường

Bạn có thể chuyển các bí mật thông qua các biến môi trường, nhưng chúng sẽ hiển thị trong tất cả các quy trình con, vùng chứa được liên kết và nhật ký cũng như thông qua docker inspect. Việc cập nhật chúng cũng rất khó khăn.

$ docker run --detach --env "DATABASE_PASSWORD=SuperSecretSauce" python:3.9-slim

d92cf5cf870eb0fdbf03c666e7fcf18f9664314b79ad58bc7618ea3445e39239


$ docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' d92cf5cf870eb0fdbf03c666e7fcf18f9664314b79ad58bc7618ea3445e39239

DATABASE_PASSWORD=SuperSecretSauce
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=C.UTF-8
GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568
PYTHON_VERSION=3.9.7
PYTHON_PIP_VERSION=21.2.4
PYTHON_SETUPTOOLS_VERSION=57.5.0
PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/c20b0cfd643cd4a19246ccf204e2997af70f6b21/public/get-pip.py
PYTHON_GET_PIP_SHA256=fa6f3fb93cce234cd4e8dd2beb54a51ab9c247653b52855a48dd44e6b21ff28b

Đây là cách tiếp cận đơn giản nhất để quản lý bí mật. Mặc dù nó không phải là cách an toàn nhất, nhưng nó sẽ giữ cho những người lương thiện trung thực vì nó cung cấp một lớp bảo vệ mỏng, giúp giữ bí mật bị che giấu khỏi những cặp mắt lang thang tò mò.

Passing secrets in using a shared volume is a better solution, but they should be encrypted, via Vault or AWS Key Management Service (KMS), since they are saved to disc.

Build-time Arguments

You can pass secrets in at build-time using build-time arguments, but they will be visible to those who have access to the image via docker history.

Example:

FROM python:3.9-slim

ARG DATABASE_PASSWORD

Build:

$ docker build --build-arg "DATABASE_PASSWORD=SuperSecretSauce" .

If you only need to use the secrets temporarily as part of the build -- i.e., SSH keys for cloning a private repo or downloading a private package -- you should use a multi-stage build since the builder history is ignored for temporary stages:

# temp stage
FROM python:3.9-slim as builder

# secret
ARG SSH_PRIVATE_KEY

# install git
RUN apt-get update && \
    apt-get install -y --no-install-recommends git

# use ssh key to clone repo
RUN mkdir -p /root/.ssh/ && \
    echo "${PRIVATE_SSH_KEY}" > /root/.ssh/id_rsa
RUN touch /root/.ssh/known_hosts &&
    ssh-keyscan bitbucket.org >> /root/.ssh/known_hosts
RUN git clone git@github.com:testdrivenio/not-real.git


# final stage
FROM python:3.9-slim

WORKDIR /app

# copy the repository from the temp image
COPY --from=builder /your-repo /app/your-repo

# use the repo for something!

Bản dựng nhiều giai đoạn chỉ giữ lại lịch sử cho hình ảnh cuối cùng. Hãy nhớ rằng bạn có thể sử dụng chức năng này cho các bí mật vĩnh viễn mà bạn cần cho ứng dụng của mình, chẳng hạn như thông tin xác thực cơ sở dữ liệu.

Bạn cũng có thể sử dụng --secrettùy chọn mới trong bản dựng Docker để chuyển các bí mật đến các hình ảnh Docker không được lưu trữ trong hình ảnh.

# "docker_is_awesome" > secrets.txt

FROM alpine

# shows secret from default secret location:
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret

Điều này sẽ gắn kết bí mật từ secrets.txttệp.

Xây dựng hình ảnh:

docker build --no-cache --progress=plain --secret id=mysecret,src=secrets.txt .

# output
...
#4 [1/2] FROM docker.io/library/alpine
#4 sha256:665ba8b2cdc0cb0200e2a42a6b3c0f8f684089f4cd1b81494fbb9805879120f7
#4 CACHED

#5 [2/2] RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
#5 sha256:75601a522ebe80ada66dedd9dd86772ca932d30d7e1b11bba94c04aa55c237de
#5 0.635 docker_is_awesome#5 DONE 0.7s

#6 exporting to image

Cuối cùng, hãy kiểm tra lịch sử để xem liệu bí mật có bị rò rỉ hay không:

❯ docker history 49574a19241c
IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
49574a19241c   5 minutes ago   CMD ["/bin/sh"]                                 0B        buildkit.dockerfile.v0
<missing>      5 minutes ago   RUN /bin/sh -c cat /run/secrets/mysecret # b…   0B        buildkit.dockerfile.v0
<missing>      4 weeks ago     /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>      4 weeks ago     /bin/sh -c #(nop) ADD file:aad4290d27580cc1a…   5.6MB

Để biết thêm về các bí mật về thời gian xây dựng, hãy xem lại Không làm rò rỉ các bí mật về xây dựng hình ảnh Docker của bạn .

Bí mật Docker

Nếu đang sử dụng Docker Swarm , bạn có thể quản lý các bí mật bằng các bí mật Docker .

Ví dụ, init Docker Swarm mode:

$ docker swarm init

Tạo bí mật về docker:

$ echo "supersecretpassword" | docker secret create postgres_password -
qdqmbpizeef0lfhyttxqfbty0

$ docker secret ls
ID                          NAME                DRIVER    CREATED         UPDATED
qdqmbpizeef0lfhyttxqfbty0   postgres_password             4 seconds ago   4 seconds ago

Khi một vùng chứa được cấp quyền truy cập vào bí mật trên, nó sẽ gắn kết tại /run/secrets/postgres_password. Tệp này sẽ chứa giá trị thực của bí mật trong bản rõ.

Sử dụng một công cụ phân tích khác nhau?

  1. AWS EKS - Sử dụng các bí mật của AWS Secrets Manager với Kubernetes
  2. DigitalOcean Kubernetes - Các bước được đề xuất để bảo mật một Cụm Kubernetes DigitalOcean
  3. Google Kubernetes Engine - Sử dụng Trình quản lý bí mật với các sản phẩm khác
  4. Nomad - Tích hợp Vault và Truy xuất Bí mật Động

Sử dụng tệp .dockerignore

Chúng tôi đã đề cập đến việc sử dụng tệp .dockerignore một vài lần rồi. Tệp này được sử dụng để chỉ định các tệp và thư mục mà bạn không muốn thêm vào ngữ cảnh xây dựng ban đầu được gửi đến trình nền Docker, sau đó sẽ xây dựng hình ảnh của bạn. Nói cách khác, bạn có thể sử dụng nó để xác định bối cảnh xây dựng mà bạn cần.

Khi hình ảnh Docker được tạo, toàn bộ ngữ cảnh Docker - tức là gốc của dự án của bạn - sẽ được gửi đến trình nền Docker trước khi các lệnh COPYhoặc ADDđược đánh giá. Điều này có thể khá tốn kém, đặc biệt nếu bạn có nhiều phụ thuộc, tệp dữ liệu lớn hoặc tạo tác phẩm xây dựng trong dự án của mình. Ngoài ra, Docker CLI và daemon có thể không nằm trên cùng một máy. Vì vậy, nếu daemon được thực thi trên một máy tính từ xa, bạn nên chú ý hơn nữa đến kích thước của ngữ cảnh xây dựng.

Bạn nên thêm gì vào tệp .dockerignore ?

  1. Tệp và thư mục tạm thời
  2. Xây dựng nhật ký
  3. Bí mật địa phương
  4. Các tệp phát triển cục bộ như docker-compost.yml
  5. Các thư mục kiểm soát phiên bản như ".git", ".hg" và ".svn"

Thí dụ:

**/.git
**/.gitignore
**/.vscode
**/coverage
**/.env
**/.aws
**/.ssh
Dockerfile
README.md
docker-compose.yml
**/.DS_Store
**/venv
**/env

Tóm lại, .dockerignore có cấu trúc đúng có thể giúp:

  1. Giảm kích thước của hình ảnh Docker
  2. Tăng tốc quá trình xây dựng
  3. Ngăn chặn việc vô hiệu hóa bộ nhớ cache không cần thiết
  4. Ngăn chặn rò rỉ bí mật

Lint và quét Dockerfiles và hình ảnh của bạn

Linting là quá trình kiểm tra mã nguồn của bạn để tìm các lỗi lập trình và văn phong cũng như các hoạt động không tốt có thể dẫn đến các sai sót tiềm ẩn. Cũng giống như các ngôn ngữ lập trình, các tệp tĩnh cũng có thể được in ra. Đặc biệt với Dockerfiles của bạn, linters có thể giúp đảm bảo chúng có thể bảo trì được, tránh cú pháp không dùng nữa và tuân thủ các phương pháp hay nhất. Linting hình ảnh của bạn phải là một phần tiêu chuẩn của đường ống CI của bạn.

Hadolint là trình liên kết Dockerfile phổ biến nhất:

$ hadolint Dockerfile

Dockerfile:1 DL3006 warning: Always tag the version of an image explicitly
Dockerfile:7 DL3042 warning: Avoid the use of cache directory with pip. Use `pip install --no-cache-dir <package>`
Dockerfile:9 DL3059 info: Multiple consecutive `RUN` instructions. Consider consolidation.
Dockerfile:17 DL3025 warning: Use arguments JSON notation for CMD and ENTRYPOINT arguments

Bạn có thể xem nó hoạt động trực tuyến tại https://hadolint.github.io/hadolint/ . Ngoài ra còn có một phần mở rộng mã VS.

Bạn có thể kết hợp các tệp Dockerfiles của mình bằng cách quét các hình ảnh và vùng chứa để tìm các lỗ hổng.

Một số tùy chọn:

  1. Snyk là nhà cung cấp độc quyền quét lỗ hổng bảo mật cho Docker. Bạn có thể sử dụng docker scanlệnh CLI để quét hình ảnh.
  2. Trivy có thể được sử dụng để quét hình ảnh vùng chứa, hệ thống tệp, kho lưu trữ git và các tệp cấu hình khác.
  3. Clair là một dự án mã nguồn mở được sử dụng để phân tích tĩnh các lỗ hổng trong các vùng chứa ứng dụng.
  4. Anchore là một dự án mã nguồn mở cung cấp dịch vụ tập trung để kiểm tra, phân tích và chứng nhận hình ảnh vùng chứa.

Tóm lại, lint và quét các tệp Dockerfiles và hình ảnh của bạn để xác định bất kỳ vấn đề tiềm ẩn nào khác với các phương pháp hay nhất.

Ký tên và xác minh hình ảnh

Làm thế nào để bạn biết rằng những hình ảnh được sử dụng để chạy mã sản xuất của bạn không bị giả mạo?

Giả mạo có thể xảy ra qua đường dây thông qua các cuộc tấn công man-in-the-middle (MITM) hoặc từ việc đăng ký bị xâm phạm hoàn toàn.

Docker Content Trust (DCT) cho phép ký và xác minh hình ảnh Docker từ các cơ quan đăng ký từ xa.

Để xác minh tính toàn vẹn và tính xác thực của hình ảnh, hãy đặt biến môi trường sau:

DOCKER_CONTENT_TRUST=1

Bây giờ, nếu bạn cố gắng kéo một hình ảnh chưa được ký, bạn sẽ nhận được lỗi sau:

Error: remote trust data does not exist for docker.io/namespace/unsigned-image:
notary.docker.io does not have trust data for docker.io/namespace/unsigned-image

Bạn có thể tìm hiểu về hình ảnh ký từ tài liệu Hình ảnh ký với Docker Content Trust .

Khi tải xuống hình ảnh từ Docker Hub, hãy đảm bảo sử dụng hình ảnh chính thức hoặc hình ảnh đã được xác minh từ các nguồn đáng tin cậy. Các nhóm lớn hơn nên sử dụng sổ đăng ký vùng chứa riêng nội bộ của riêng họ .

Mẹo thưởng

Sử dụng môi trường ảo Python

Bạn có nên sử dụng môi trường ảo bên trong vùng chứa không?

Trong hầu hết các trường hợp, môi trường ảo là không cần thiết miễn là bạn chỉ chạy một quy trình duy nhất cho mỗi vùng chứa. Vì bản thân vùng chứa cung cấp khả năng cách ly, các gói có thể được cài đặt trên toàn hệ thống. Điều đó nói rằng, bạn có thể muốn sử dụng môi trường ảo trong một bản dựng nhiều giai đoạn hơn là xây dựng các tệp bánh xe.

Ví dụ với bánh xe:

# temp stage
FROM python:3.9-slim as builder

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc

COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt


# final stage
FROM python:3.9-slim

WORKDIR /app

COPY --from=builder /app/wheels /wheels
COPY --from=builder /app/requirements.txt .

RUN pip install --no-cache /wheels/*

Ví dụ với virtualenv:

# temp stage
FROM python:3.9-slim as builder

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc

RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

COPY requirements.txt .
RUN pip install -r requirements.txt


# final stage
FROM python:3.9-slim

COPY --from=builder /opt/venv /opt/venv

WORKDIR /app

ENV PATH="/opt/venv/bin:$PATH"

Đặt giới hạn bộ nhớ và CPU

It's a good idea to limit the memory usage of your Docker containers, especially if you're running multiple containers on a single machine. This can prevent any of the containers from using all available memory and thereby crippling the rest.

The easiest way to limit memory usage is to use --memory and --cpu options in the Docker cli:

$ docker run --cpus=2 -m 512m nginx

The above command limits the container usage to 2 CPUs and 512 megabytes of main memory.

You can do the same in a Docker Compose file like so:

version: "3.9"
services:
  redis:
    image: redis:alpine
    deploy:
      resources:
        limits:
          cpus: 2
          memory: 512M
        reservations:
          cpus: 1
          memory: 256M

Take note of the reservations field. It's used to set a soft limit, which takes priority when the host machine has low memory or CPU resources.

Additional resources:

  1. Runtime options with Memory, CPUs, and GPUs
  2. Docker Compose resouce constraints

Log to stdout or stderr

Các ứng dụng chạy trong vùng chứa Docker của bạn nên ghi thông báo nhật ký vào đầu ra tiêu chuẩn (stdout) và lỗi tiêu chuẩn (stderr) chứ không phải vào tệp.

Sau đó, bạn có thể định cấu hình daemon Docker để gửi các thông báo nhật ký của bạn đến một giải pháp ghi nhật ký tập trung (như CloudWatch Logs hoặc Papertrail ).

Để biết thêm, hãy xem Xử lý nhật ký dưới dạng luồng sự kiện từ Ứng dụng Mười hai nhân tố và Định cấu hình trình điều khiển ghi nhật ký từ tài liệu Docker.

Sử dụng một giá đỡ bộ nhớ dùng chung cho Gunicorn Heartbeat

Gunicorn sử dụng một hệ thống nhịp tim dựa trên tệp để đảm bảo rằng tất cả các quy trình nhân viên đã phân tách đều hoạt động.

Trong hầu hết các trường hợp, các tệp nhịp tim được tìm thấy trong "/ tmp", thường nằm trong bộ nhớ thông qua tmpfs . Vì Docker không tận dụng tmpfs theo mặc định, các tệp sẽ được lưu trữ trên hệ thống tệp được sao lưu bằng đĩa. Điều này có thể gây ra sự cố , chẳng hạn như đóng băng ngẫu nhiên do hệ thống nhịp tim sử dụng os.fchmod, điều này có thể chặn nhân viên nếu thực tế thư mục nằm trên hệ thống tệp được hỗ trợ bằng đĩa.

May mắn thay, có một cách khắc phục đơn giản: Thay đổi thư mục nhịp tim thành thư mục được ánh xạ bộ nhớ thông qua --worker-tmp-dircờ.

gunicorn --worker-tmp-dir /dev/shm config.wsgi -b 0.0.0.0:8000

Sự kết luận

Bài viết này xem xét một số phương pháp hay nhất để làm cho Dockerfiles và hình ảnh của bạn sạch hơn, gọn gàng hơn và an toàn hơn.

Nguồn:  https://testdriven.io

#python #docker 

Shardul Bhatt

Shardul Bhatt

1626775355

Why use Python for Software Development

No programming language is pretty much as diverse as Python. It enables building cutting edge applications effortlessly. Developers are as yet investigating the full capability of end-to-end Python development services in various areas. 

By areas, we mean FinTech, HealthTech, InsureTech, Cybersecurity, and that's just the beginning. These are New Economy areas, and Python has the ability to serve every one of them. The vast majority of them require massive computational abilities. Python's code is dynamic and powerful - equipped for taking care of the heavy traffic and substantial algorithmic capacities. 

Programming advancement is multidimensional today. Endeavor programming requires an intelligent application with AI and ML capacities. Shopper based applications require information examination to convey a superior client experience. Netflix, Trello, and Amazon are genuine instances of such applications. Python assists with building them effortlessly. 

5 Reasons to Utilize Python for Programming Web Apps 

Python can do such numerous things that developers can't discover enough reasons to admire it. Python application development isn't restricted to web and enterprise applications. It is exceptionally adaptable and superb for a wide range of uses.

Robust frameworks 

Python is known for its tools and frameworks. There's a structure for everything. Django is helpful for building web applications, venture applications, logical applications, and mathematical processing. Flask is another web improvement framework with no conditions. 

Web2Py, CherryPy, and Falcon offer incredible capabilities to customize Python development services. A large portion of them are open-source frameworks that allow quick turn of events. 

Simple to read and compose 

Python has an improved sentence structure - one that is like the English language. New engineers for Python can undoubtedly understand where they stand in the development process. The simplicity of composing allows quick application building. 

The motivation behind building Python, as said by its maker Guido Van Rossum, was to empower even beginner engineers to comprehend the programming language. The simple coding likewise permits developers to roll out speedy improvements without getting confused by pointless subtleties. 

Utilized by the best 

Alright - Python isn't simply one more programming language. It should have something, which is the reason the business giants use it. Furthermore, that too for different purposes. Developers at Google use Python to assemble framework organization systems, parallel information pusher, code audit, testing and QA, and substantially more. Netflix utilizes Python web development services for its recommendation algorithm and media player. 

Massive community support 

Python has a steadily developing community that offers enormous help. From amateurs to specialists, there's everybody. There are a lot of instructional exercises, documentation, and guides accessible for Python web development solutions. 

Today, numerous universities start with Python, adding to the quantity of individuals in the community. Frequently, Python designers team up on various tasks and help each other with algorithmic, utilitarian, and application critical thinking. 

Progressive applications 

Python is the greatest supporter of data science, Machine Learning, and Artificial Intelligence at any enterprise software development company. Its utilization cases in cutting edge applications are the most compelling motivation for its prosperity. Python is the second most well known tool after R for data analytics.

The simplicity of getting sorted out, overseeing, and visualizing information through unique libraries makes it ideal for data based applications. TensorFlow for neural networks and OpenCV for computer vision are two of Python's most well known use cases for Machine learning applications.

Summary

Thinking about the advances in programming and innovation, Python is a YES for an assorted scope of utilizations. Game development, web application development services, GUI advancement, ML and AI improvement, Enterprise and customer applications - every one of them uses Python to its full potential. 

The disadvantages of Python web improvement arrangements are regularly disregarded by developers and organizations because of the advantages it gives. They focus on quality over speed and performance over blunders. That is the reason it's a good idea to utilize Python for building the applications of the future.

#python development services #python development company #python app development #python development #python in web development #python software development

Art  Lind

Art Lind

1602968400

Python Tricks Every Developer Should Know

Python is awesome, it’s one of the easiest languages with simple and intuitive syntax but wait, have you ever thought that there might ways to write your python code simpler?

In this tutorial, you’re going to learn a variety of Python tricks that you can use to write your Python code in a more readable and efficient way like a pro.

Let’s get started

Swapping value in Python

Instead of creating a temporary variable to hold the value of the one while swapping, you can do this instead

>>> FirstName = "kalebu"
>>> LastName = "Jordan"
>>> FirstName, LastName = LastName, FirstName 
>>> print(FirstName, LastName)
('Jordan', 'kalebu')

#python #python-programming #python3 #python-tutorials #learn-python #python-tips #python-skills #python-development

Art  Lind

Art Lind

1602666000

How to Remove all Duplicate Files on your Drive via Python

Today you’re going to learn how to use Python programming in a way that can ultimately save a lot of space on your drive by removing all the duplicates.

Intro

In many situations you may find yourself having duplicates files on your disk and but when it comes to tracking and checking them manually it can tedious.

Heres a solution

Instead of tracking throughout your disk to see if there is a duplicate, you can automate the process using coding, by writing a program to recursively track through the disk and remove all the found duplicates and that’s what this article is about.

But How do we do it?

If we were to read the whole file and then compare it to the rest of the files recursively through the given directory it will take a very long time, then how do we do it?

The answer is hashing, with hashing can generate a given string of letters and numbers which act as the identity of a given file and if we find any other file with the same identity we gonna delete it.

There’s a variety of hashing algorithms out there such as

  • md5
  • sha1
  • sha224, sha256, sha384 and sha512

#python-programming #python-tutorials #learn-python #python-project #python3 #python #python-skills #python-tips

How To Compare Tesla and Ford Company By Using Magic Methods in Python

Magic Methods are the special methods which gives us the ability to access built in syntactical features such as ‘<’, ‘>’, ‘==’, ‘+’ etc…

You must have worked with such methods without knowing them to be as magic methods. Magic methods can be identified with their names which start with __ and ends with __ like init, call, str etc. These methods are also called Dunder Methods, because of their name starting and ending with Double Underscore (Dunder).

Now there are a number of such special methods, which you might have come across too, in Python. We will just be taking an example of a few of them to understand how they work and how we can use them.

1. init

class AnyClass:
    def __init__():
        print("Init called on its own")
obj = AnyClass()

The first example is _init, _and as the name suggests, it is used for initializing objects. Init method is called on its own, ie. whenever an object is created for the class, the init method is called on its own.

The output of the above code will be given below. Note how we did not call the init method and it got invoked as we created an object for class AnyClass.

Init called on its own

2. add

Let’s move to some other example, add gives us the ability to access the built in syntax feature of the character +. Let’s see how,

class AnyClass:
    def __init__(self, var):
        self.some_var = var
    def __add__(self, other_obj):
        print("Calling the add method")
        return self.some_var + other_obj.some_var
obj1 = AnyClass(5)
obj2 = AnyClass(6)
obj1 + obj2

#python3 #python #python-programming #python-web-development #python-tutorials #python-top-story #python-tips #learn-python