Giới thiệu về Linux Namespaces Image Giới thiệu về Linux Namespaces

1. Namespaces là gì?

Linux Namespaces trên Docker

Linux Namespaces là một công nghệ cốt yếu phía sau hầu hết các container runtime hiện nay. Namespaces là một tính năng của Linux Kernel để phân chia các tài nguyên hệ thống như một tập hợp tiến trình(process) chỉ thấy một tập hợp các tài nguyên tương ứng. Ví dụ như, PID namespace cô lập(isolate) vùng số cấp cho ID của tiến trình, có nghĩa là 2 tiến trình trên cùng một máy chủ(host), nhưng ở khác namespace có thể có cùng ID tiến trình.

Mức độ cô lập(isolation) này rõ ràng rất hữu ích với container. Nếu không có namespaces, tiến trình chạy ở container A có thể nhìn thấy toàn bộ tiến trình của container B, hoặc nghiêm trọng hơn container A có quyền umount một số filesystem quan trọng ở những container khác trên cùng một máy chủ. Với namespaces, các tiến trình ở container A sẽ nhận thức sự tồn tại tiến trình ở các container khác.

Nói cách khác, bạn không thể giao tiếp với những gì mà bạn không nhìn thấy và đó là những gì thực sự tính năng Namespaces trên hệ điều hành Linux cung cấp - một cách để hạn chế những gì một tiến trình có thể nhìn thấy, để làm cho nó xuất hiện như thể nó là tiến trình duy nhất đang chạy trên máy chủ.

Lưu ý: Namespaces không giới hạn truy cập vào các tài nguyên vật lý như CPU, bộ nhớ, đĩa cứng. Các truy cập này được đo đạc và giới hạn bởi một tính tăng của Linux Kernel gọi là "cgroups".

Bạn có thể dùng các ngôn ngữ lập trình như C hoặc Golang để hiểu hơn về Namespaces trên Linux ở mức độ low-level. Tuy nhiên đối với các bạn không am hiểu sâu về lập trình thì sẽ gặp khó khăn, may mắn là trên Linux có lệnh unshare, lệnh này giúp bạn chạy một chương trình mới ở trong namespace riêng của nó, không chia sẻ(unshare) với tiến trình nào khác. Dưới đây mình sẽ ví dụ cho các bạn về UTS Namespace dùng lệnh unshare này, UTS Namespace là một namespace để cô lập các thiết lập liên quan đến hostname và domainname nhận diện của hệ thống:

[root@bravo-k8s ~]# hostname             # Kiểm tra hostname hiện tại
bravo-k8s.novalocal
[root@bravo-k8s ~]# unshare -u /bin/bash # Chạy một tiến trình bash shell mới dùng UTS namespace riêng
sh-4.2# hostname hostname-new            # Set hostname mới ở shell mới này
sh-4.2# hostname                         # Kiểm tra hostname mới vừa set
hostname-new
sh-4.2# exit                             # Thoát ra khỏi tiến trình này
exit
[root@bravo-k8s ~]# hostname             # Kiểm tra lại lần nữa, cho thấy hostname ban đầu không bị thay đổi.
bravo-k8s.novalocal

Còn đây là ví dụ về PID Namespace:

[root@bravo-k8s ~]# unshare --fork --pid  --mount-proc /bin/bash
[root@bravo-k8s ~]# ps axuf # Tiến trình bash shell mới chỉ thấy nó là duy nhất, thậm chí PID của nó là 1
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0 115436  1996 pts/2    S    16:10   0:00 /bin/bash
root        10  0.0  0.0 155320  1760 pts/2    R+   16:10   0:00 ps axuf
[root@bravo-k8s ~]#

Từ đầu bài viết đến giờ chúng ta chỉ mới đề cập tới PID Namespace và UTS Namespace, tuy nhiên tính đến thời điểm viết bài này thì đã có tổng cộng 7 loại Namespaces trên Linux:

  • Mount - cô lập các filesystem mount point
  • UTS - cô lập hostname và domainname
  • IPC - cô lập tài nguyên giao tiếp liên tiến trình(IPC)
  • PID - cô lập vùng số cấp cho ID của tiến trình
  • Network - cô lập giao diện mạng
  • User - cô lập về UID/GID
  • Cgroup - cô lập về thư mục root của tính năng cgroups, chỉ mới xuất hiện từ Linux Kernel phiên bản 4.6 trở đi

2. Các công cụ quản lý Namespaces trên Linux

Để liệt kê các Namespaces trên máy chủ bạn có thể dùng công cụ lsns:

[root@bravo-k8s ~]# lsns
        NS TYPE  NPROCS   PID USER   COMMAND
4026531836 pid      130     1 root   /usr/lib/systemd/systemd --switched-root --system --deserialize 22
4026531837 user     167     1 root   /usr/lib/systemd/systemd --switched-root --system --deserialize 22
4026531838 uts      130     1 root   /usr/lib/systemd/systemd --switched-root --system --deserialize 22
4026531839 ipc      128     1 root   /usr/lib/systemd/systemd --switched-root --system --deserialize 22
4026531840 mnt      122     1 root   /usr/lib/systemd/systemd --switched-root --system --deserialize 22
4026531856 mnt        1    13 root   kdevtmpfs
4026531956 net      134     1 root   /usr/lib/systemd/systemd --switched-root --system --deserialize 22
4026532184 mnt        1   380 root   /usr/lib/systemd/systemd-udevd
4026532194 mnt        1   507 chrony /usr/sbin/chronyd
4026532195 mnt        3   544 root   /usr/sbin/NetworkManager --no-daemon
4026532196 mnt        1  9589 root   /pause
4026532197 uts        1  9589 root   /pause
4026532198 ipc        2  9589 root   /pause
4026532199 pid        1  9589 root   /pause
4026532201 net        2  9589 root   /pause
4026532264 mnt        1  1373 root   /pause
4026532265 uts        1  1373 root   /pause
4026532266 ipc        2  1373 root   /pause
4026532267 pid        1  1373 root   /pause
4026532268 mnt        1  1375 root   /pause
4026532269 uts        1  1375 root   /pause
4026532270 ipc        2  1375 root   /pause
4026532271 pid        1  1375 root   /pause
4026532272 mnt        1  1818 root   /opt/bin/flanneld --ip-masq --kube-subnet-mgr
4026532273 pid        1  1818 root   /opt/bin/flanneld --ip-masq --kube-subnet-mgr
......

Chúng ta có thể cho một tiến trình mới tham gia(join) vào namespace đang có dùng công cụ nsenter, ví dụ sau chạy lệnh /bin/sh để tạo một shell mới ở namespace của tiến trình của ví dụ về PID Namespace:

[root@bravo-k8s ~]# nsenter -t 9897 --mount --uts --ipc --net --pid sh
sh-4.2#
sh-4.2# ps axuf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root        10  0.0  0.0 115432  1832 pts/0    S    16:35   0:00 sh
root        11  0.0  0.0 155320  1760 pts/0    R+   16:35   0:00  \_ ps axuf
root         1  0.0  0.0 115436  1996 pts/2    S+   16:29   0:00 /bin/bash

Như chúng ta thấy tiến trình /bin/sh mới có thể truy cập vào một namespace sẵn có và nhìn thấy các tiến trình đang chạy ở namespace này, nó hoạt động bằng cách dùng syscall setns ở Linux Kernel, đây cũng là cách các "sidecar" container hoạt động để giao tiếp với App container trên Kubernetes.

Lệnh unshare giúp bạn khá dễ hiểu về Namespaces trên Linux, nhưng thao tác của nó khá hạn chế. Trong các bài tiếp xem mình sẽ giới thiệu Namespaces trong các ngôn ngữ lập trình để các bạn có thể tạo ra một "containerized process" hay "mini docker".