Chi tiết kỹ thuật xung quanh Docker Link

Hệ thống --link là một di sản của Docker, kế thừa từ các phiên bản trước. Bất chấp thực tế rằng chức năng này có thể được hủy bỏ bất kỳ lúc này, nhiều nền tảng phát triển phần mềm hiện tại vẫn đang dựa trên nó (chẳng hạn cấu hình service của GitLab CI).

Link là một chức năng gắn liền với mạng Docker Bridge mặc định. Trước khi các chức năng Docker Network ra đời, link là giải pháp để các container nhận thức và giao nhận thông tin với nhau một cách an toàn.

Bài viết này sẽ làm rõ bức tranh toàn cảnh về link.

Kết nối container thông qua port-mapping

Đặt vấn đề với một ứng dụng Python Flash đơn giản chạy trong một container:

$ docker run -d -P training/webapp python app.py
nostalgic_morse

Các container có network và IP nội bộ của chúng, cờ -P được sử dụng để tự động ánh xạ bất cứ port mở nào của container tới một port ngẫu nhiên nằm trong dải ephemeral ports của máy host. Sự ánh xạ này có thể thấy được khi chạy docker ps, trong ví dụ dưới đây port 5000 của container đã được dẫn tới port 49155 của host:

$ docker ps nostalgic_morse

CONTAINER ID  IMAGE                   COMMAND       CREATED        STATUS        PORTS                    NAMES
bc533791f3f5  training/webapp:latest  python app.py 5 seconds ago  Up 2 seconds  0.0.0.0:49155->5000/tcp  nostalgic_morse

Ánh xạ có thể được cấu hình thủ công thông qua cờ -p. Ví dụ dưới đây ánh xạ port 5000 của container tới port 80 của host:

$ docker run -d -p 80:5000 training/webapp python app.py

Nếu việc ánh xạ vào một port cố định gây phát sinh vấn đề, thì có thể tự định nghĩa một dải port riêng để Docker chọn ngẫu nhiên:

$ docker run -d -p 8000-9000:5000 training/webapp python app.py

Mặc định, ánh xạ được thực hiện trên tất cả các interface của máy host. Nếu cần thiết thì có thể chỉ định interface được ánh xạ, trong ví dụ dưới đây, port 5000 được ánh xạ vào port 80, nhưng chỉ trên interface localhost:

$ docker run -d -p 127.0.0.1:80:5000 training/webapp python app.py

Hoặc một kết hợp khác, vào một port bất kỳ trong dải ephemeral ports, chỉ trên interface localhost:

$ docker run -d -p 127.0.0.1::5000 training/webapp python app.py

Thậm chí có thể chỉ định port UDP hay SCTP bằng cách sử dụng hậu tố /udp or /sctp:

$ docker run -d -p 127.0.0.1:80:5000/udp training/webapp python app.py

Lệnh docker port có thể được sử dụng để hiển thị port đang được ánh xạ:

$ docker port nostalgic_morse 5000
127.0.0.1:49155

Kết nối thông qua linking system

Lưu ý: nội dung ở phần này liên quan rất chặt chẽ đến mạng bridge mặc định.

Docker phân giải các liên kết dựa theo tên của các container. Tên có thể được Docker tạo ra một cách tự động, mà trong bài viết này là nostalgic_morse, hoặc được đặt thủ công. Tên tạo ra hai chức năng hữu ích:

  1. Làm rõ ý nghĩa về mặt chức năng của container, chẳng hạn đặt tên container chứa web applicationweb.
  2. Cung cấp cho Docker một điểm tham chiếu đến container, chẳng hạn container web có thể liên kết đến container db thông qua tên. Chức năng này đến từ đặc tính đơn nhất của tên container.

Tên có thể được đặt thủ công bằng cờ --name chẳng hạn:

$ docker run -d -P --name web training/webapp python app.py

Tên của container có thể được kiểm tra bằng cách sử dụng câu lệnh docker ps:

$ docker ps -l

CONTAINER ID  IMAGE                  COMMAND        CREATED       STATUS       PORTS                    NAMES
aed84ee21bde  training/webapp:latest python app.py  12 hours ago  Up 2 seconds 0.0.0.0:49154->5000/tcp  web

Giao tiếp thông qua các link

Các link cho phép các container nhận thức được sự tồn tại của các container khác và giao vận thông tin một cách an toàn từ container này tới container kia. Khi cài đặt một link, một đường dẫn sẽ được tạo ra giữa container nguồn và container nhận. Bên nhận có thể truy cập dữ liệu từ bên nguồn. Để tạo một link, sử dụng cờ --link. Dưới đây là ví dụ với một container có tên db được tạo ra từ một ảnh có chứa CSDL PostgreSQL:

$ docker run -d --name db training/postgres

Container web được tạo ra và liên kết với db thông qua một link:

$ docker run -d -P --name web --link db:db training/webapp python app.py

Cú pháp sử dụng cờ --link có dạng sau:

--link <name or id>:alias

Trong đó name là tên của container được liên kết tới, và alias là bí danh cho link. Bí danh có thể được đặt tắt:

--link <name or id>

Trong trường hợp này link nhận bí danh giống với tên của container. Ví dụ ở trên có thể được viết lại như sau:

$ docker run -d -P --name web --link db training/webapp python app.py

Thử inspect container web:

$ docker inspect -f "{{ .HostConfig.Links }}" web
[/db:/web/db]

Kết quả cho thấy container web đã được liên kết tới container db, cho phép web truy xuất dữ liệu của db.

Điều mà link thực sự làm là gì? Docker tạo ra một sercure tunnel giữa hai container mà không yêu cầu bất kỳ thao tác mở port nào; vậy là web có thể truy cập db, mà không yêu cầu db phải phơi bày bất kỳ thứ gì với mạng.

Docker expose thông tin kết nối của bên nguồn với bên nhận bằng hai đường:

  • Các biến môi trường
  • Cập nhật tập tin /etc/hosts

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

Docker tạo ra một vài biến môi trường trên container bên nhận khi thực hiện link. Đầu tiên là các biến môi trường đến từ tham số của cờ --link. Phần còn lại đến từ:

  • các lệnh ENV từ Dockerfile của container bên nguồn
  • các tùy chọn -e, --env--env-file của câu lệnh docker run đã khởi động container nguồn

Cơ chế này cho phép lập trình để nhận thức được các thông tin cần thiết của container nguồn từ container đích.

Cảnh báo: phải nắm thật rõ ràng cơ chế expose biến môi trường của container được link. Điều này có thể gây rò rỉ dữ liệu nhạy cảm.

Docker đặt một biến môi trường <alias>_NAME cho mỗi container đích của cờ --link. Lấy ví dụ, container web được link tới container db bởi --link db:webdb, vậy Docker sẽ tạo ra biến môi trường WEBDB_NAME=/web/webdb vào container web.

Docker cũng định nghĩa một tập các biến môi trường cho mỗi port được expose bởi container nguồn. Mỗi biến có một tiền tố đơn nhất theo dạng <name>_PORT_<port>_<protocol>, trong đó:

  • bí danh <name> được chỉ định bởi cờ --link (ví dụ webdb)
  • <port> thể hiện port được expose
  • <protocol> chỉ định giao thức TCP hay UDP

Docker sử dụng định dạng tiền tố này để định nghĩa ba biến môi trường khác nhau cho mỗi port:

  • Biến prefix_ADDR chứa địa chỉ IP từ URL, ví dụ WEBDB_PORT_5432_TCP_ADDR=172.17.0.82.
  • Biến prefix_PORT chứa số hiệu của port từ URL, ví dụ WEBDB_PORT_5432_TCP_PORT=5432.
  • Biết prefix_PROTO chứa giao thức từ URL, ví dụ WEBDB_PORT_5432_TCP_PROTO=tcp.

Ngoài ra, Docker cũng tạo một biến môi trường có tên theo định dạng <alias>_PORT. Biến này chứa URL của port đầu tiên được container nguồn expose, trong đó port “đầu tiên” nghĩa là port có số hiệu thấp nhất. Ví dụ, WEBDB_PORT=tcp://172.17.0.82:5432. Nếu port được sử dụng trên cả giao thức tcpudp thì giao thức tcp sẽ được chọn.

Cuối cùng, Docker cũng expose mỗi biến môi trường bắt nguồn từ container nguồn thành các biến môi trường tương ứng tại container đích. Các biến tại container đích được đặt tên theo định dạng <alias>_EVN_<name>, trong đó <name> là tên biến nguyên thủy. Giá trị của biến sẽ lấy tự giá trị được sử dụng khi khởi động container nguồn.

Lệnh env có thể được sử dụng để liệt kê danh sách các biến môi trường của container chỉ định

    $ docker run --rm --name web2 --link db:db training/webapp env

    . . .
    DB_NAME=/web2/db
    DB_PORT=tcp://172.17.0.5:5432
    DB_PORT_5432_TCP=tcp://172.17.0.5:5432
    DB_PORT_5432_TCP_PROTO=tcp
    DB_PORT_5432_TCP_PORT=5432
    DB_PORT_5432_TCP_ADDR=172.17.0.5
    . . .

Trong ví dụ trên là một loạt biến môi trường với các thông tin hữu dụng về container nguồn. Tất cả đều bắt đầu với tiền tố DB_ – vốn là alias của --link. Các biến này có thể được sử dụng để kết nối ứng dụng trong container đích đến CSDL tại container db. Sự kết nối là riêng tư và được bảo mật; chỉ container web có thể giao tiếp với db theo cách này.

Lưu ý quan trọng về các biến môi trường

Không giống như các bản ghi của tập tin /etc/hosts, các địa chỉ IP được chứa trong biến môi trường sẽ không được cập nhật nếu container nguồn khởi động lại. Nên sử dụng bản ghi của /etc/hosts để phân giải địa chỉ IP của container được liên kết.

Các biến môi trường chỉ được đặt cho process đầu tiên thực thi trong container. Một vài daemon, chẳng hạn sshd, sẽ không nhìn thấy các biến này.

Cập nhật /etc/hosts

Bên cạnh các biến môi trường, Docker bổ sung một bản ghi cho mỗi container nguồn vào /etc/hosts. Chẳng hạn như dưới đây là bản ghi mà container web nắm giữ:

$ docker run -t -i --rm --link db:webdb training/webapp /bin/bash

root@aed84ee21bde:/opt/webapp# cat /etc/hosts

172.17.0.7  aed84ee21bde
. . .
172.17.0.5  webdb 6e5cdeb2d300 db

Ở trên có hai bản ghi. Bản ghi đầu tiên để phân giải hostname (chính là ID) của container web. Bản ghi thứ hai phân giải bí danh, tên, và hostname của container db:

root@aed84ee21bde:/opt/webapp# apt-get install -yqq inetutils-ping

root@aed84ee21bde:/opt/webapp# ping webdb

PING webdb (172.17.0.5): 48 data bytes
56 bytes from 172.17.0.5: icmp_seq=0 ttl=64 time=0.267 ms
56 bytes from 172.17.0.5: icmp_seq=1 ttl=64 time=0.250 ms
56 bytes from 172.17.0.5: icmp_seq=2 ttl=64 time=0.256 ms

Ví dụ trên đã cài đặt công cụ và sau đó ping đến db thông qua bí danh. Bí danh này có thể được dùng để cấu hình cho ứng dụng chạy trong web truy cập đến db.

Nếu container db được khởi động lại, tập tin /etc/hosts trên container web sẽ được tự động cập nhật.

$ docker restart db

db

$ docker run -t -i --rm --link db:db training/webapp /bin/bash

root@aed84ee21bde:/opt/webapp# cat /etc/hosts

172.17.0.7  aed84ee21bde
. . .
172.17.0.9  db

Tài nguyên tham khảo

_

Leave a Comment

Your email address will not be published. Required fields are marked *