Dalam rangkaian posting ini, kami akan mempertimbangkan dengan hati-hati salah satu bahan utama dalam wadah - ruang nama. Dalam prosesnya, kita akan membuat klon sederhana dari docker run
- program kita sendiri yang akan mengambil perintah (bersama dengan argumennya, jika ada) pada input dan memperluas wadah untuk pelaksanaannya, terisolasi dari sisa sistem, mirip dengan bagaimana Anda akan mengeksekusi docker run
untuk lari dari sebuah gambar .
Apa itu namespace?
Linux namespace adalah abstraksi sumber daya dalam sistem operasi. Kita dapat menganggap namespace sebagai sebuah kotak. Kotak ini berisi sumber daya sistem yang bergantung pada jenis kotak (namespace). Saat ini ada tujuh jenis ruang nama: Grup, IPC, Jaringan, Mount, PID, Pengguna, UTS.
Misalnya, namespace jaringan mencakup sumber daya sistem terkait jaringan seperti antarmuka jaringan (mis. wlan0
, eth0
), tabel routing, dll., Mount namespace menyertakan file dan direktori dalam sistem, PID berisi ID proses, dan sebagainya. . Dengan demikian, dua contoh Network namespace A dan B (sesuai dengan dua kotak dari jenis yang sama dalam analogi kami) dapat berisi sumber daya yang berbeda - mungkin A berisi wlan0
, sementara B berisi eth0
dan salinan terpisah dari tabel routing.
Ruang nama bukan beberapa fitur atau pustaka yang perlu Anda instal, misalnya, menggunakan manajer paket apt. Mereka disediakan oleh kernel Linux itu sendiri dan sudah merupakan keharusan untuk menjalankan proses apa pun pada sistem. Pada suatu titik waktu tertentu, setiap proses P milik tepat satu contoh namespace dari setiap jenis. Oleh karena itu, ketika dia perlu mengatakan "perbarui tabel perutean di sistem", Linux menunjukkan kepadanya salinan tabel perutean namespace tempat dia berada saat itu.
Untuk apa ini?
Sama sekali tidak ada ... tentu saja, saya hanya bercanda. Salah satu properti hebat kotak adalah Anda dapat menambah dan menghapus barang-barang dari kotak dan ini tidak akan mempengaruhi isi kotak lainnya. Ini adalah ide yang sama dengan namespaces - proses P mungkin “menjadi gila” dan mengeksekusi sudo rm –rf /
, tetapi proses Q lain milik Mount namespace tidak akan terpengaruh, karena mereka menggunakan salinan terpisah dari file-file ini.
Perhatikan bahwa sumber daya yang terkandung dalam namespace belum tentu merupakan salinan unik. Dalam beberapa kasus yang terjadi secara sengaja atau karena pelanggaran keamanan, dua ruang nama atau lebih akan berisi salinan yang sama, misalnya, file yang sama. Dengan demikian, perubahan yang dibuat untuk file ini dalam satu Mount namespace sebenarnya akan terlihat di semua Mount namespaces lainnya, yang juga merujuk padanya. Karenanya, kami akan mengabaikan analogi laci kami, karena item tersebut tidak boleh berada di dua kotak yang berbeda secara bersamaan.
Pembatasan menjadi perhatian
Kita bisa melihat ruang nama tempat proses itu berada! Biasanya untuk Linux, mereka muncul sebagai file di direktori /proc/$pid/ns
dari proses ini dengan proses id $pid
:
$ ls -l /proc/$$/ns total 0 lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 cgroup -> cgroup:[4026531835] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 ipc -> ipc:[4026531839] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 mnt -> mnt:[4026531840] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 net -> net:[4026531957] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 pid -> pid:[4026531836] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 user -> user:[4026531837] lrwxrwxrwx 1 iffy iffy 0 May 18 12:53 uts -> uts:[4026531838]
Anda dapat membuka terminal lain, jalankan perintah yang sama dan ini akan memberi Anda hasil yang sama. Ini karena, seperti yang kami sebutkan sebelumnya, proses tersebut harus dimiliki oleh namespace tertentu (namespace) dan sampai kami secara eksplisit menentukan yang mana, Linux menambahkannya ke namespace secara default.
Mari kita sedikit terlibat dalam hal ini. Di terminal kedua, kita bisa melakukan sesuatu seperti ini:
$ hostname iffy $ sudo unshare -u bash $ ls -l /proc/$$/ns lrwxrwxrwx 1 root root 0 May 18 13:04 cgroup -> cgroup:[4026531835] lrwxrwxrwx 1 root root 0 May 18 13:04 ipc -> ipc:[4026531839] lrwxrwxrwx 1 root root 0 May 18 13:04 mnt -> mnt:[4026531840] lrwxrwxrwx 1 root root 0 May 18 13:04 net -> net:[4026531957] lrwxrwxrwx 1 root root 0 May 18 13:04 pid -> pid:[4026531836] lrwxrwxrwx 1 root root 0 May 18 13:04 user -> user:[4026531837] lrwxrwxrwx 1 root root 0 May 18 13:04 uts -> uts:[4026532474] $ hostname iffy $ hostname coke $ hostname coke
Perintah unshare
meluncurkan program (opsional) di namespace baru. Bendera -u
memberitahunya untuk menjalankan bash
di namespace UTS baru. Perhatikan bahwa proses bash
baru kami menunjuk ke file uts
lain, sementara yang lain tetap sama.
Membuat ruang nama baru biasanya membutuhkan akses pengguna super. unshare
, kami akan berasumsi bahwa baik unshare
dan implementasi kami dilakukan dengan menggunakan sudo
.
Salah satu konsekuensi dari apa yang baru saja kita lakukan adalah bahwa sekarang kita dapat mengubah nama host sistem dari proses bash baru kita dan ini tidak akan mempengaruhi proses lain dalam sistem. Anda dapat memverifikasi ini dengan menjalankan hostname
di terminal pertama dan melihat bahwa nama host belum berubah di sana.
Tapi apa, misalnya, wadah itu?
Semoga sekarang Anda memiliki ide tentang apa yang bisa dilakukan namespace. Anda dapat mengasumsikan bahwa wadah pada dasarnya adalah proses biasa dengan ruang nama yang berbeda dari proses lain, dan Anda akan benar. Sebenarnya, ini adalah kuota. Wadah tanpa kuota tidak harus termasuk dalam namespace unik dari masing-masing jenis - wadah dapat berbagi sebagian darinya.
Misalnya, ketika Anda mengetik docker run --net=host redis
, yang Anda lakukan adalah memberi tahu buruh pelabuhan untuk tidak membuat namespace Jaringan baru untuk proses redis. Dan, seperti yang telah kita lihat, Linux akan menambahkan proses ini sebagai peserta dalam namespace Jaringan default, seperti proses reguler lainnya. Dengan demikian, dari sudut pandang jaringan, proses redis persis sama dengan yang lainnya. Ini bukan hanya opsi konfigurasi jaringan, docker run
memungkinkan Anda membuat perubahan seperti itu untuk sebagian besar ruang nama yang ada. Ini menimbulkan pertanyaan, apa itu wadah? Apakah ada wadah yang menggunakan proses yang menggunakan semua kecuali satu dari namespace yang umum? ¯ \ _ (ツ) _ / ¯ Biasanya, wadah datang dengan konsep isolasi yang dicapai melalui ruang nama: semakin sedikit jumlah ruang nama dan sumber daya yang proses bagi dengan orang lain, semakin terisolasi dan semakin penting.
Isolasi
Di sisa posting ini, kita akan meletakkan fondasi untuk program kita, yang kita sebut isolate
. isolate
mengambil perintah sebagai argumen dan memulainya dalam proses baru, terisolasi dari bagian lain sistem dan dibatasi oleh ruang namanya sendiri. Dalam posting berikut, kita akan melihat menambahkan dukungan untuk ruang nama individual untuk perintah proses yang isolate
dimulai.
Bergantung pada aplikasinya, kami akan fokus pada ruang nama Pengguna, Mount, PID dan Jaringan. Sisanya akan relatif sepele untuk diterapkan setelah kami selesai (pada kenyataannya, kami akan menambahkan dukungan UTS di sini dalam implementasi awal program). Dan pertimbangan, misalnya, dari Cgroup, berada di luar cakupan seri ini (studi tentang cgroup, komponen lain dari wadah yang digunakan untuk mengontrol berapa banyak sumber daya yang dapat digunakan oleh suatu proses).
Ruang nama dapat berubah menjadi sangat cepat dan ada banyak cara yang dapat Anda gunakan saat menjelajahi setiap ruang nama, tetapi kami tidak dapat memilih semuanya sekaligus. Kami hanya akan membahas cara-cara yang relevan dengan program yang kami kembangkan. Setiap posting akan dimulai dengan beberapa percobaan di konsol di namespace yang dimaksud untuk memahami langkah-langkah yang diperlukan untuk mengkonfigurasi namespace ini. Sebagai hasilnya, kita akan sudah memiliki gagasan tentang apa yang ingin kita capai, dan kemudian implementasi yang sesuai secara isolate
akan mengikuti.
Untuk menghindari kelebihan kode pada posting, kami tidak akan memasukkan hal-hal seperti fungsi tambahan yang tidak diperlukan untuk memahami implementasi. Anda dapat menemukan kode sumber lengkap di sini di Github .
Implementasi
Kode sumber untuk posting ini dapat ditemukan di sini . Implementasi isolate
kami akan menjadi program sederhana yang membaca baris dengan perintah dari stdin dan mengkloning proses baru yang mengeksekusinya dengan argumen yang ditentukan. Proses kloning dengan perintah akan berjalan di namespace UTS sendiri dengan cara yang sama seperti yang kita lakukan dengan unshare
. Dalam posting berikutnya kita akan melihat bahwa ruang nama tidak harus bekerja (atau setidaknya menyediakan isolasi) dari kotak dan kita perlu melakukan beberapa konfigurasi setelah membuatnya (tetapi sebelum benar-benar menjalankan perintah), sehingga perintah benar-benar berjalan dalam isolasi.
Kombinasi create-configure namespace ini akan memerlukan beberapa interaksi antara proses isolate
utama dan proses isolate
dari perintah untuk dijalankan. Akibatnya, bagian dari pekerjaan utama di sini adalah mengkonfigurasi saluran penghubung antara kedua proses - dalam kasus kami, kami akan menggunakan pipa Linux karena kesederhanaannya.
Kita perlu melakukan tiga hal:
- Buat proses
isolate
dasar yang membaca data dari stdin. - Klon proses baru yang akan menjalankan perintah di namespace UTS baru.
- Konfigurasikan pipa sehingga proses eksekusi perintah memulai peluncurannya hanya setelah menerima sinyal dari proses utama sehingga konfigurasi namespace selesai.
Inilah proses dasarnya:
int main(int argc, char **argv) { struct params params; memset(¶ms, 0, sizeof(struct params)); parse_args(argc, argv, ¶ms);
Perhatikan clone_flags
yang kami sampaikan ke panggilan clone
kami. Lihat betapa mudahnya membuat proses di ruang namanya sendiri? Yang perlu kita lakukan hanyalah mengatur flag untuk tipe namespace (flag CLONE_NEWUTS
sesuai dengan namespace UTS), dan Linux akan mengurus sisanya.
Selanjutnya, proses perintah mengharapkan sinyal sebelum dimulai:
static int cmd_exec(void *arg) {
Akhirnya, kita dapat mencoba menjalankan ini:
$ ./isolate sh ===========sh============ $ ls isolate isolate.c isolate.o Makefile $ hostname iffy $ hostname coke $ hostname coke
Sekarang isolate
sedikit lebih dari program yang hanya forkes tim (kami memiliki UTS yang bekerja untuk kami). Dalam posting berikutnya, kita akan mengambil langkah lain dengan memeriksa ruang nama pengguna dan membuat isolate
menjalankan perintah di ruang nama pengguna sendiri. Di sana kita akan melihat bahwa kita benar-benar perlu melakukan beberapa pekerjaan untuk memiliki namespace yang dapat digunakan di mana perintah dapat dieksekusi.