Docker container hiện được sử dụng phổ biến để dễ dàng tạo ra các application instance mà dễ triển khai và nhân rộng. Trong số các case, có một trường hợp đặc biệt đó là triển khai Docker container trong một Docker container khác.

TL;DR: Tại sao?

Một trong số những xuất phát kinh điển của case này là CI.

Bạn đã xây dựng application của bạn như một cụm các service cô lập chạy trong docker container. Bạn đã build một CI slave (hay CI runner — tùy theo cách gọi của bạn) để thực thi các CI stages dựa trên các container này.

docker

Giờ, bạn có nhu cầu container hóa cả CI slave của bạn.

Có nhiều nguyên nhân dẫn đến mong muốn này, có thể là bởi bạn muốn dễ bảo trì, dễ tái tạo, hay nhân rộng, thế này chẳng hạn:

Không chỉ có CI, đôi khi bạn đơn giản chỉ muốn cô lập hóa toàn bộ cụm service của bạn khỏi môi trường tại host, concept tổng thể sẽ không quá khác biệt với các sơ đồ ở trên. Và dù là gì đi chăng nữa thì cấu trúc container in container cũng đã xuất hiện. Và bài viết này đề cập đến các giải pháp để hiện thực hóa vấn đề đó.

Rắc rối

Container trong container nghĩa là bạn cần có một Docker daemon hoạt động trong một container. Việc này không đơn giản như là chạy một máy ảo trong một máy ảo khác, Docker không hoạt động như thế.

Trong mỗi container không tồn tại kernel hay các system daemon và Docker daemon sẽ đứng ra làm nhiệm vụ liên lạc trung gian giữa application bên trong service với các system daemon và kernel. Nhưng nếu trong môi trường không có những thứ này thì Docker daemon không có gì để làm việc với cả. Đó chính là điều xảy ra với Docker in Docker, và giải quyết vấn đề đó khó, khó đấy.

docker

DinD

DinD, hay Docker-in-Docker là giải pháp đầu tiên cho vấn đề này. Về mặt định nghĩa, Dind là một ảnh Doker container được xây dựng (bởi chính đội ngũ nhà phát triển Docker) mà trong đó có cài đặt sẵn một Docker daemon hoạt động được.

docker

Dind rất dễ sử dụng, ngoại trừ một vấn đề duy nhất: vì lý do kỹ thuật, container Dind cần được cấu hình như một privileged (đặc quyền) container. Giải thích về container đặc quyền khá rắc rối, giải thích tại sao đó là vấn đề thì dễ hơn: một khi được cấp đặc quyền, các process trong container có quyền hạn tương đương với quyền root ở bên ngoài container. Điều này đặc biệt nguy hiểm trong những trường hợp mà bạn không quản lý được “thứ gì thực sự chạy ở trong container”, việc bạn bị tấn công và dữ liệu ở Host bị rò rỉ là hoàn toàn có thể xảy ra.

Chính vì lý do này mà Dind không được khuyến khích sử dụng (bởi chính các nhà phát triển Docker) mặc dù nó được hỗ trợ chính thức.

DooD

Dood, hay Docker-out-of-Docker là một cách tiếp cận khác dựa trên khái niệm Docker socket. Vậy nên trước tiên chúng ta nói về socket.

Docker socket

Socket thường ám chỉ tới socket mạng, chúng là những thứ được gắn với một port hay một địa chỉ mạng, chúng ta gửi request tới socket, và nhận response từ socket. Khi bạn bật webapp lên tại localhost:9000 chính là bạn đang tạo ra một socket. Chúng ta gọi đây là TCP socket.

Các hệ điều hành Unix có một khái niệm khác nữa về socket. Đó là những đầu mối giao tiếp giữa các tiến trình khác nhau trong hệ thống (cứ thử tưởng tượng trình Power Point giao tiếp với Excel để cùng vẽ nên một biểu đồ). Thực chất thì các socket này chỉ đơn thuần là các file, tiến trình A ghi nội dung vào một file, và tiến trình B đọc file đó. Chúng ta gọi đây là Unix socket.

Docker là một chương trình có thiết kế client-server. Lệnh docker bạn thường sử dụng chính là client, còn Docker daemon thì đóng vai trò làm server. Server này nghe các request bằng cả TCP socket lẫn Unix socket. Unix socket của Docker (là cái file đó) thì được được đặt tại var/run/docker.sock.

Mấu chốt quan trọng nhất rút ra được ở đây là: nếu bạn làm việc được với file docker.sock thì có nghĩa là bạn đang làm việc được với Docker daemon đứng sau nó.

Docker out of Docker

Vậy là bạn có giải pháp khác: thay vì bạn đưa một Docker daemon vào trong container thì bạn trao cho môi trường bên trong container khả năng làm việc với Docker daemon ở bên ngoài. Việc này quá dễ dàng nếu kết hợp cơ chế volumn mounting với Unix socket:

$ docker run -it -v /var/run/docker.sock:/var/run/docker.sock XCode language: JavaScript (javascript)

Và chỉ cần X là bất kỳ container nào mà có sẵn docker client (dùng luôn ảnh _/docker cũng được) là bạn có thể bật các service được rồi.

Một hệ quả phụ của giải pháp này là mặc dù Docker client ở trong container là tác nhân phát lệnh triển khai các service, nhưng Docker daemon bên ngoài mới là tác nhân thực thi, do đó thực chất các service được khởi tạo ở ngoài container.

Hệ quả này có thể gây đến một số bất lợi như:

  • Khả năng Docker client ở bên trong ráng tạo ra một service trùng tên với một service đã tồn tại sẵn ở bên ngoài.
  • Các cấu hình mount mà Docker client ở bên trong đặt ra thực chất được xem xét và thực hiện tại file system của host. Hãy nghĩ đến khả năng bạn ở Hà Nội và gọi điện cho bạn của bạn ở Sài Gòn ra quảng trường Ba Đình xem pháo bông.
  • Tương tự, cấu hình port mapping mà Docker client ở bên trong đặt ra thực chất được xem xét và thực hiện tại cơ sở socket của host.

Chính bởi các bất lợi trên mà giải pháp này không dùng được khi bạn muốn triển khai container của bạn bởi các orchestra như Kubernetes. Chuyện này nói sau, bạn có thể hiểu đơn giản là Kubernetes mất khả năng giám sát và điều khiển hoạt động của container của bạn.

Rủi ro bảo mật cũng không phải không có, bằng việc mount docker.sock , bạn đã trao cho Docker client ở bên trong khả năng thao túng bất cứ container nào đang hoạt động ở host.

Tùy thuộc vào môi trường mà cấu hình của bạn mà các rủi ro có thể tiêu biến. Nhưng bạn phải biết bạn đang thực sự làm gì.

Kết

Tóm lại, có một số ý chính mà bạn có thể ghi nhớ lại:

  • Nhu cầu thực thi Docker in Docker là có tồn tại, và bạn sẽ thường xuyên đối mặt
  • DinD là giải pháp chính thức, và DinD cần được khởi động như một container đặc quyền (privileged). Điều này có thể là vấn đề hoặc không.
  • Một giải pháp thay thế là DooD, được triển khai bằng cách mount docker.sock. Điều này cũng có thể là vấn đề, hoặc không.
  • Nếu bạn có thể build được, hay tìm được một ảnh container mà có Docker daemon của riêng nó (không phải DooD), nhưng thực thi mà không cần đặc quyền, vấn đề của cả hai giải pháp trên sẽ được giải quyết.

Author: Nguyễn Bình Sơn