Cách gắn khối lượng máy chủ vào các thùng chứa docker trong Dockerfile trong quá trình xây dựng


236

Câu hỏi ban đầu: Làm thế nào để sử dụng lệnh VOLUME trong Dockerfile?

Câu hỏi thực tế tôi muốn giải quyết là - làm thế nào để gắn khối lượng máy chủ vào các thùng chứa docker trong Dockerfile trong quá trình xây dựng, tức là có docker run -v /export:/exportkhả năng trong khi docker build.

Lý do đằng sau nó, đối với tôi, là khi xây dựng mọi thứ trong Docker, tôi không muốn những apt-get installbộ đệm ( ) đó bị khóa trong một docker duy nhất, mà là để chia sẻ / tái sử dụng chúng. Đó là lý do chính tôi đang hỏi về câu hỏi này.

Cập nhật mới nhất:

Trước docker v18.09, câu trả lời đúng phải là câu bắt đầu bằng:

Có một cách để gắn kết âm lượng trong quá trình xây dựng, nhưng nó không liên quan đến Dockerfiles.

Tuy nhiên, đó là một câu trả lời kém, có tổ chức và được hỗ trợ. Khi tôi cài đặt lại docker của mình, tôi tình cờ gặp phải bài viết sau:

Dockerize một dịch vụ apt-cacher-ng
https://docs.docker.com/engine/examples/apt-cacher-ng/

Đó là giải pháp của docker cho câu hỏi này / của tôi, không trực tiếp mà là gián tiếp. Đó là cách docker chính thống gợi ý chúng ta làm. Và tôi thừa nhận nó tốt hơn cái tôi đang cố hỏi ở đây.

Một cách khác là, câu trả lời mới được chấp nhận , ví dụ: Buildkit trong v18.09.

Chọn bất cứ điều gì phù hợp với bạn.


Là: Đã có một giải pháp - rocker, vốn không phải từ Docker, nhưng bây giờ rocker đã bị ngưng, tôi hoàn nguyên lại câu trả lời là "Không thể" .


Cập nhật cũ: Vì vậy, câu trả lời là "Không thể". Tôi có thể chấp nhận nó như một câu trả lời vì tôi biết vấn đề đã được thảo luận rộng rãi tại https://github.com/docker/docker/issues/3156 . Tôi có thể hiểu rằng tính di động là một vấn đề tối quan trọng đối với nhà phát triển docker; nhưng là một người dùng docker, tôi phải nói rằng tôi rất thất vọng về tính năng bị thiếu này. Hãy để tôi kết thúc cuộc tranh luận của mình bằng một trích dẫn từ cuộc thảo luận đã nói ở trên: " Tôi muốn sử dụng Gentoo làm hình ảnh cơ bản nhưng chắc chắn không muốn> 1GB dữ liệu cây Portage ở bất kỳ lớp nào sau khi hình ảnh được tạo. có thể có một số thùng chứa nhỏ gọn nếu không phải là cây portage khổng lồ phải xuất hiện trong ảnh trong quá trình cài đặt."Có, tôi có thể sử dụng wget hoặc curl để tải xuống bất cứ thứ gì tôi cần, nhưng thực tế là chỉ xem xét tính di động hiện đang buộc tôi phải tải xuống> 1GB cây Portage mỗi khi tôi xây dựng hình ảnh cơ sở Gentoo không hiệu quả cũng không thân thiện với người dùng. hơn nữa, kho lưu trữ gói LUÔN LUÔN nằm dưới / usr / portage, do đó LUÔN LUÔN CÓ THỂ theo Gentoo. Một lần nữa, tôi tôn trọng quyết định, nhưng xin vui lòng cho phép tôi bày tỏ sự thất vọng của mình trong thời gian đó.


Câu hỏi gốc chi tiết:

Từ

Chia sẻ thư mục qua các tập
http://docker.readthedocs.org/en/v0.7.3/use/usiness_with_volume/

nó nói rằng tính năng khối lượng dữ liệu "đã có sẵn từ phiên bản 1 của Docker Remote API". Docker của tôi là phiên bản 1.2.0, nhưng tôi thấy ví dụ đưa ra trong bài viết trên không hoạt động:

# BUILD-USING:        docker build -t data .
# RUN-USING:          docker run -name DATA data
FROM          busybox
VOLUME        ["/var/volume1", "/var/volume2"]
CMD           ["/usr/bin/true"]

Cách thích hợp trong Dockerfile để gắn các khối lượng gắn trên máy chủ vào các thùng chứa docker, thông qua lệnh VOLUME là gì?

$ apt-cache policy lxc-docker
lxc-docker:
  Installed: 1.2.0
  Candidate: 1.2.0
  Version table:
 *** 1.2.0 0
        500 https://get.docker.io/ubuntu/ docker/main amd64 Packages
        100 /var/lib/dpkg/status

$ cat Dockerfile 
FROM          debian:sid

VOLUME        ["/export"]
RUN ls -l /export
CMD ls -l /export

$ docker build -t data .
Sending build context to Docker daemon  2.56 kB
Sending build context to Docker daemon 
Step 0 : FROM          debian:sid
 ---> 77e97a48ce6a
Step 1 : VOLUME        ["/export"]
 ---> Using cache
 ---> 59b69b65a074
Step 2 : RUN ls -l /export
 ---> Running in df43c78d74be
total 0
 ---> 9d29a6eb263f
Removing intermediate container df43c78d74be
Step 3 : CMD ls -l /export
 ---> Running in 8e4916d3e390
 ---> d6e7e1c52551
Removing intermediate container 8e4916d3e390
Successfully built d6e7e1c52551

$ docker run data
total 0

$ ls -l /export | wc 
     20     162    1131

$ docker -v
Docker version 1.2.0, build fa7b24f

Rõ ràng yêu cầu tính năng hiện tại nhiều hơn (không phải tôi mong đợi nó sẽ được thực hiện, nhưng chỉ trong trường hợp): docker / docker # 14080
Jesse Glick

thực sự có một cuộc thảo luận rộng rãi rằng nó không nên được phép liên kết một thư mục máy chủ và thư mục container trong quá trình xây dựng tức là một cái gì đó như thế VOLUME ~/host_dir ~/container_dir. Các cuộc thảo luận khá rộng rãi, si có một cách ngắn gọn để tóm tắt lý do là gì?
Charlie Parker

Câu trả lời:


33

Đầu tiên, để trả lời "tại sao không VOLUMEhoạt động?" Khi bạn xác định một VOLUMEtrong Dockerfile, bạn chỉ có thể xác định mục tiêu, không phải là nguồn của âm lượng. Trong quá trình xây dựng, bạn sẽ chỉ nhận được một khối lượng ẩn danh từ này. Khối lượng ẩn danh đó sẽ được gắn vào mỗi RUNlệnh, được điền sẵn nội dung của hình ảnh và sau đó bị loại bỏ ở cuối RUNlệnh. Chỉ thay đổi đối với container được lưu, không thay đổi âm lượng.


Vì câu hỏi này đã được hỏi, một vài tính năng đã được phát hành có thể giúp ích. Đầu tiên là các bản dựng nhiều tầng cho phép bạn xây dựng một không gian đĩa không hiệu quả ở giai đoạn đầu tiên và chỉ sao chép đầu ra cần thiết vào giai đoạn cuối cùng mà bạn gửi. Và tính năng thứ hai là Buildkit đang thay đổi đáng kể cách thức hình ảnh được xây dựng và các khả năng mới đang được thêm vào bản dựng.

Đối với bản dựng nhiều giai đoạn, bạn sẽ có nhiều FROMdòng, mỗi dòng bắt đầu tạo một hình ảnh riêng biệt. Chỉ hình ảnh cuối cùng được gắn thẻ theo mặc định, nhưng bạn có thể sao chép các tệp từ các giai đoạn trước. Việc sử dụng tiêu chuẩn là có một môi trường trình biên dịch để xây dựng một tạo phẩm ứng dụng nhị phân hoặc ứng dụng khác và môi trường thời gian chạy là giai đoạn thứ hai sao chép trên tạo phẩm đó. Bạn có thể có:

FROM debian:sid as builder
COPY export /export
RUN compile command here >/result.bin

FROM debian:sid
COPY --from=builder /result.bin /result.bin
CMD ["/result.bin"]

Điều đó sẽ dẫn đến một bản dựng chỉ chứa nhị phân kết quả và không chứa thư mục đầy đủ / xuất.


Buildkit sắp ra mắt thử nghiệm vào ngày 18/9. Đây là một thiết kế lại hoàn chỉnh của quá trình xây dựng, bao gồm khả năng thay đổi trình phân tích cú pháp frontend. Một trong những thay đổi của trình phân tích cú pháp đã triển khai RUN --mounttùy chọn cho phép bạn gắn thư mục bộ đệm cho các lệnh chạy của mình. Ví dụ: đây là một thư mục gắn kết một số thư mục debian (với cấu hình lại của hình ảnh debian, điều này có thể tăng tốc độ cài đặt lại các gói):

# syntax = docker/dockerfile:experimental
FROM debian:latest
RUN --mount=target=/var/lib/apt/lists,type=cache \
    --mount=target=/var/cache/apt,type=cache \
    apt-get update \
 && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
      git

Bạn sẽ điều chỉnh thư mục bộ đệm cho bất kỳ bộ đệm ứng dụng nào bạn có, ví dụ: $ HOME / .m2 cho maven hoặc /root/.cache cho golang.


TL; DR: Trả lời ở đây: Với RUN --mountcú pháp đó , bạn cũng có thể liên kết các thư mục chỉ đọc gắn kết từ bối cảnh xây dựng. Thư mục phải tồn tại trong ngữ cảnh bản dựng và nó không được ánh xạ trở lại máy chủ hoặc máy khách bản dựng:

# syntax = docker/dockerfile:experimental
FROM debian:latest
RUN --mount=target=/export,type=bind,source=export \
    process export directory here...

Lưu ý rằng vì thư mục được gắn từ ngữ cảnh, nó cũng được gắn ở chế độ chỉ đọc và bạn không thể đẩy các thay đổi trở lại máy chủ hoặc máy khách. Khi bạn xây dựng, bạn sẽ muốn cài đặt 18,09 hoặc mới hơn và kích hoạt bộ xây dựng với export DOCKER_BUILDKIT=1.

Nếu bạn gặp lỗi mà cờ gắn kết không được hỗ trợ, điều đó cho thấy rằng bạn đã không kích hoạt bộ công cụ xây dựng với biến ở trên hoặc bạn đã không bật cú pháp thử nghiệm với dòng cú pháp ở đầu Dockerfile trước đó bất kỳ dòng nào khác, bao gồm cả ý kiến. Lưu ý rằng biến để chuyển đổi bộ công cụ xây dựng sẽ chỉ hoạt động nếu cài đặt docker của bạn có hỗ trợ bộ công cụ tích hợp, yêu cầu phiên bản 18.09 hoặc mới hơn từ Docker, cả trên máy khách và máy chủ.


2
Thật không may, trên Windows Buildkit chưa được hỗ trợ trong phiên bản 18.09
Wesley

1
Có vẻ như "armhf" cũng không hỗ trợ "mount".
Mike

2
Tôi đang nhận được "Phản hồi lỗi từ daemon: Dockerfile parse dòng lỗi xx: Cờ không xác định: mount" trên OSX
ChristoKiwi

1
Hỗ trợ cho docker-compose chưa có, nhưng bạn không cần soạn để xây dựng hình ảnh. Vấn đề cần theo dõi: github.com/moby/buildkit/issues/685
BMitch


116

Không thể sử dụng VOLUMEhướng dẫn để nói với docker những gì cần gắn kết. Điều đó sẽ phá vỡ nghiêm trọng tính di động. Hướng dẫn này cho docker biết rằng nội dung trong các thư mục đó không đi vào hình ảnh và có thể được truy cập từ các thùng chứa khác bằng --volumes-fromtham số dòng lệnh. Bạn phải chạy container bằng cách -v /path/on/host:/path/in/containertruy cập các thư mục từ máy chủ.

Gắn khối lượng máy chủ trong quá trình xây dựng là không thể. Không có đặc quyền xây dựng và gắn kết máy chủ cũng sẽ làm giảm nghiêm trọng tính di động. Bạn có thể muốn thử sử dụng wget hoặc curl để tải xuống bất cứ thứ gì bạn cần cho bản dựng và đặt nó vào vị trí.


2
Cảm ơn. Câu hỏi sửa đổi. Câu hỏi thực tế tôi muốn giải quyết là - làm thế nào để gắn khối lượng máy chủ vào các thùng chứa docker trong Dockerfile trong quá trình xây dựng. Cám ơn.
xpt

2
Không thể. Xem câu trả lời sửa đổi.
Andreas Steffan

3
Tôi có thể đánh giá cao các tác dụng phụ "tiềm năng" đối với tính di động, nhưng cũng có trường hợp sử dụng hợp lệ để có tùy chọn này. Trong trường hợp của tôi, tôi rất muốn có thể nói với người dùng "Di chuyển đến thư mục và chạy lệnh 'docker run'" có $ (PWD) được gắn vào một số thư mục chứa. $ (PWD) đảm bảo tính di động được duy trì. Mặc dù đây có thể là một trường hợp góc, nhưng nó sẽ giúp tôi rất nhiều khi tôi phân phối môi trường thời gian chạy cho các tập lệnh do người dùng cung cấp.
ntwrkguru

64

CẬP NHẬT: Ai đó sẽ không nhận được câu trả lời, và tôi rất thích nó, đặc biệt là cho câu hỏi đặc biệt này.

TIN TỨC TỐT, có một cách bây giờ -

Giải pháp là Rocker: https://github.com/grammarly/rocker

John Yani nói , "IMO, nó giải quyết tất cả các điểm yếu của Dockerfile, làm cho nó phù hợp để phát triển."

Rocker

https://github.com/grammarly/rocker

Bằng cách giới thiệu các lệnh mới, Rocker nhằm mục đích giải quyết các trường hợp sử dụng sau, điều gây đau khổ với Docker đơn giản:

  1. Gắn khối lượng có thể sử dụng lại trên giai đoạn xây dựng, vì vậy các công cụ quản lý phụ thuộc có thể sử dụng bộ đệm giữa các bản dựng.
  2. Chia sẻ khóa ssh với bản dựng (để kéo repos riêng, v.v.), trong khi không để chúng trong hình ảnh kết quả.
  3. Xây dựng và chạy ứng dụng trong các hình ảnh khác nhau, có thể dễ dàng chuyển một tạo phẩm từ hình ảnh này sang hình ảnh khác, lý tưởng là có logic này trong một Dockerfile duy nhất.
  4. Tag / Đẩy hình ảnh ngay từ Dockerfiles.
  5. Truyền các biến từ lệnh shell build để chúng có thể được thay thế vào Dockerfile.

Và hơn thế nữa. Đây là những vấn đề quan trọng nhất đã cản trở việc chúng tôi áp dụng Docker tại Grammarly.

Cập nhật: Rocker đã bị ngừng, theo bản repo dự án chính thức trên Github

Tính đến đầu năm 2018, hệ sinh thái container đã trưởng thành hơn nhiều so với ba năm trước khi dự án này được khởi xướng. Bây giờ, một số tính năng quan trọng và nổi bật của rocker có thể dễ dàng được bao phủ bởi việc xây dựng docker hoặc các công cụ được hỗ trợ tốt khác, mặc dù một số tính năng vẫn duy nhất cho rocker. Xem https://github.com/grammarly/rocker/issues/199 để biết thêm chi tiết.


Tôi đang cố gắng sử dụng Rocker để giải quyết vấn đề số 1 nhưng lệnh mount sẽ không hoạt động và hình ảnh được tạo không chứa thư mục máy chủ. Lệnh gắn kết Dockerfile của tôi trông như thế này - MOUNT ~/code/docker-app-dev/new-editor/:/src/và lệnh xây dựng Rocker của tôi là thế này - rocker build -f Dockerfile .. Tôi đã làm gì sai?
Yaron Idan

Có thể thử sử dụng một đường dẫn máy chủ thực sự? ~là một metacharacter vỏ Bourne.
Jesse Glick

Rocker buildkhông cho phép docker runtùy chọn dòng lệnh, vì vậy hiện tại không cho phép những thứ như --privileged.
Monty Wild

Xin chào @xpt, chúng tôi có thể nhận được một bản cập nhật khác không vì hiện tại rocker đã ngừng hoạt động
Shardj

Bây giờ rocker đã bị ngưng, tôi hoàn nguyên câu trả lời trở lại "Không thể" một lần nữa. Xem OP và câu trả lời được chọn.
xpt

14

Có một cách để gắn kết âm lượng trong quá trình xây dựng, nhưng nó không liên quan đến Dockerfiles.

Kỹ thuật sẽ là tạo một thùng chứa từ bất kỳ cơ sở nào bạn muốn sử dụng (gắn (các) âm lượng của bạn trong thùng chứa với -vtùy chọn), chạy tập lệnh shell để thực hiện công việc xây dựng hình ảnh của bạn, sau đó cam kết container như một hình ảnh khi hoàn tất .

Điều này không chỉ loại bỏ các tệp dư thừa mà bạn không muốn (điều này cũng tốt cho các tệp bảo mật, như các tệp SSH), nó cũng tạo ra một hình ảnh duy nhất. Nó có nhược điểm: lệnh commit không hỗ trợ tất cả các hướng dẫn Dockerfile và nó không cho phép bạn nhận khi bạn rời đi nếu bạn cần chỉnh sửa tập lệnh xây dựng của mình.

CẬP NHẬT:

Ví dụ,

CONTAINER_ID=$(docker run -dit ubuntu:16.04)
docker cp build.sh $CONTAINER_ID:/build.sh
docker exec -t $CONTAINER_ID /bin/sh -c '/bin/sh /build.sh'
docker commit $CONTAINER_ID $REPO:$TAG
docker stop $CONTAINER_ID

6
+1 Bạn có thể vui lòng giải thích thêm một chút về hướng dẫn trong đoạn 2 không. Ví dụ, nếu cơ sở là debian:wheezyvà tập lệnh shell là build.sh, người ta sẽ sử dụng hướng dẫn cụ thể nào?
Drux

6

Khi bạn chạy container, một thư mục trên máy chủ của bạn sẽ được tạo và gắn vào container. Bạn có thể tìm ra thư mục này với

$ docker inspect --format "{{ .Volumes }}" <ID>
map[/export:/var/lib/docker/vfs/dir/<VOLUME ID...>]

Nếu bạn muốn gắn một thư mục từ máy chủ của bạn bên trong thùng chứa của bạn, bạn phải sử dụng -vtham số và chỉ định thư mục. Trong trường hợp của bạn, điều này sẽ là:

docker run -v /export:/export data

Vì vậy, bạn sẽ sử dụng thư mục máy chủ trong container của bạn.


1
Cảm ơn. Câu hỏi sửa đổi. Câu hỏi thực tế tôi muốn giải quyết là - làm thế nào để gắn khối lượng máy chủ vào các thùng chứa docker trong Dockerfile trong quá trình xây dựng. Cám ơn.
xpt

Xin đừng sửa lại câu hỏi của bạn một cách quyết liệt như vậy . Điều này làm cho câu hỏi của tôi không hợp lệ mặc dù nó hoàn toàn hợp lệ trước các chỉnh sửa của bạn. Thay vào đó hãy xem xét một câu hỏi mới.
Behe

11
Câu hỏi ban đầu : Làm thế nào để sử dụng lệnh VOLUME trong Dockerfile? Nó vẫn còn ở đầu câu hỏi ngay cả cho đến ngày hôm nay. Câu trả lời của bạn là thời gian chạy và câu hỏi của tôi luôn là về thời gian xây dựng , đó là những gì Dockerfile dành cho.
xpt

4

Tôi nghĩ bạn có thể làm những gì bạn muốn làm bằng cách chạy bản dựng thông qua lệnh docker mà chính nó đang chạy bên trong một container docker. Xem Docker bây giờ có thể chạy trong Docker | Blog Docker . Một kỹ thuật như thế này, nhưng thực sự truy cập vào docker bên ngoài từ một container, đã được sử dụng, ví dụ, trong khi khám phá cách tạo ra Docker container nhỏ nhất có thể | Blog Xebia .

Một bài viết liên quan khác là Tối ưu hóa hình ảnh Docker | CenturyLink Labs , giải thích rằng nếu bạn kết thúc tải xuống nội dung trong quá trình xây dựng, bạn có thể tránh bị lãng phí không gian trong hình ảnh cuối cùng bằng cách tải xuống, xây dựng và xóa tất cả trong một bước CHẠY.


3

Thật là xấu xí, nhưng tôi đã đạt được một ngữ nghĩa như thế này:

Dockerfile:

FROM foo
COPY ./m2/ /root/.m2
RUN stuff

imageBuild.sh:

docker build . -t barImage
container="$(docker run -d barImage)"
rm -rf ./m2
docker cp "$container:/root/.m2" ./m2
docker rm -f "$container"

Tôi có một bản dựng java tải vũ trụ vào /root/.m2 và đã làm như vậy mỗi lần . imageBuild.shsao chép nội dung của thư mục đó vào máy chủ sau khi xây dựng và Dockerfilesao chép chúng trở lại vào hình ảnh cho bản dựng tiếp theo.

Đây là một cái gì đó giống như cách một khối lượng sẽ hoạt động (nghĩa là nó vẫn tồn tại giữa các bản dựng).


Đây là một giải pháp khả thi cho tích hợp liên tục dựa trên Docker hay còn gọi là CI. Thiết lập các thư viện và trình biên dịch và chạy make thông qua các lệnh Dockerfile, khởi chạy hình ảnh một cách tầm thường chỉ để tạo một thùng chứa, và cuối cùng sao chép các tạo phẩm mong muốn như một .deb. Có vẻ để làm việc, cảm ơn vì đã đăng bài này.
chrisinmtown

Giải pháp này để lại cho bạn một hình ảnh với TẤT CẢ các tệp trong ./m2/ - cái bạn cần và cái bạn không cần - và điều này có thể dẫn đến hình ảnh sản xuất HUGE, không mong muốn! Với việc gắn vào thư mục phụ thuộc bên ngoài, chỉ các tệp cần thiết sẽ được sao chép vào hình ảnh.
Marko Krajnc

Nếu bạn có ý định xuất bản hình ảnh, có lẽ tốt nhất là cứ chờ và để maven tải xuống phụ thuộc của chính nó mỗi lần. Việc hack này chỉ có ý nghĩa nếu bạn dàn dựng một hình ảnh để thử nghiệm - một hình ảnh mà người dùng cuối sẽ không bao giờ tiếp xúc với.
MatrixManAtYrService

1

Dưới đây là phiên bản đơn giản hóa của phương pháp 2 bước sử dụng xây dựng và cam kết, không có tập lệnh shell. Nó bao gồm:

  1. Xây dựng hình ảnh một phần, không có khối lượng
  2. Chạy một thùng chứa với khối lượng , thực hiện thay đổi, sau đó cam kết kết quả, thay thế tên hình ảnh gốc.

Với những thay đổi tương đối nhỏ, bước bổ sung chỉ thêm một vài giây vào thời gian xây dựng.

Về cơ bản:

docker build -t image-name . # your normal docker build

# Now run a command in a throwaway container that uses volumes and makes changes:
docker run -v /some:/volume --name temp-container image-name /some/post-configure/command

# Replace the original image with the result:
# (reverting CMD to whatever it was, otherwise it will be set to /some/post-configure/command)   
docker commit --change="CMD bash" temp-container image-name 

# Delete the temporary container:
docker rm temp-container

Trong trường hợp sử dụng của tôi, tôi muốn tạo trước một tệp maven toolchains.xml, nhưng nhiều bản cài đặt JDK của tôi nằm trên một khối lượng không có sẵn cho đến khi chạy. Một số hình ảnh của tôi không tương thích với tất cả các JDKS, vì vậy tôi cần kiểm tra tính tương thích tại thời điểm xây dựng và điền vào toolchains.xml một cách có điều kiện. Lưu ý rằng tôi không cần hình ảnh để có thể mang theo, tôi không xuất bản nó lên Docker Hub.


1

Như nhiều người đã trả lời, việc gắn khối lượng máy chủ trong quá trình xây dựng là không thể. Tôi chỉ muốn thêm docker-composecách, tôi nghĩ rằng nó sẽ tốt đẹp, chủ yếu là để sử dụng thử nghiệm / phát triển

Dockerfile

FROM node:10
WORKDIR /app
COPY . .
RUN npm ci
CMD sleep 999999999

docker-compose.yml

version: '3'
services:
  test-service:
    image: test/image
    build:
      context: .
      dockerfile: Dockerfile
    container_name: test
    volumes:
      - ./export:/app/export
      - ./build:/app/build

Và chạy container của bạn bằng cách docker-compose up -d --build

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.