ایجاد یک ظرف حداقل در GO: یک راهنمای گام به گام (قسمت 1)

ظروف به هر طریقی چیست!
ظروف سبک ، قابل حمل و کارآمد هستند و آنها را به یک انتخاب محبوب برای استقرار و اجرای برنامه ها تبدیل می کند. در این آموزش ، ما شما را از طریق فرآیند ایجاد یک ظرف حداقل با استفاده از GO راهنمایی می کنیم. کد مثال ارائه شده بر مفاهیم اساسی کانتینر سازی ، از جمله مکانهای نام ، کروت و گروههای کنترل (CGROUP) متمرکز است.
قبل از شروع ، اطمینان حاصل کنید که موارد زیر را نصب کرده اید:
Go programming language: Install Go
Basic understanding of Linux namespaces and control groups
مقدمه
بنابراین مکانهای نام و گروه های کنترل لینوکس چیست؟
نام های نام از حدود سال 2002 بخشی از هسته لینوکس بوده است و با گذشت زمان بیشتر به ابزارهای بیشتر و انواع فضای نام اضافه شده است. پشتیبانی کانتینر واقعی فقط در سال 2013 به هسته لینوکس اضافه شد. این همان چیزی است که فضای نام را واقعاً مفید کرده و آنها را به توده ها آورده است.
اما نام های نام دقیقاً چیست؟ در اینجا یک تعریف کلمه ای از ویکی پدیا آورده شده است:
“فضای نام از ویژگی های هسته لینوکس است که منابع هسته را تقسیم می کند به گونه ای که یک مجموعه از فرآیندها یک مجموعه از منابع را مشاهده می کند در حالی که مجموعه دیگری از فرآیندها مجموعه ای متفاوت از منابع را مشاهده می کند.”
به عبارت دیگر ، ویژگی اصلی مکانهای نام این است که آنها فرآیندها را از یکدیگر جدا می کنند. در سرور که در آن خدمات مختلفی را اجرا می کنید ، جدا کردن هر سرویس و فرآیندهای مرتبط با آن از سایر خدمات به این معنی است که شعاع انفجار کوچکتر برای تغییرات وجود دارد ، و همچنین یک ردپای کوچکتر برای نگرانی های مربوط به امنیت وجود دارد. با این حال ، بیشتر خدمات جداسازی ، مطابق با مارتین فاولر ، از سبک معماری میکروسرویسها برخوردار است.
انواع نام های نام
در هسته لینوکس انواع مختلفی از نام های نام وجود دارد. هر فضای نام ویژگی های منحصر به فرد خود را دارد:
A user namespace has its own set of user IDs and group IDs for assignment to processes. In particular, this means that a process can have root privilege within its user namespace without having it in other user namespaces.
A process ID (PID) namespace assigns a set of PIDs to processes that are independent from the set of PIDs in other namespaces. The first process created in a new namespace has PID 1 and child processes are assigned subsequent PIDs. If a child process is created with its own PID namespace, it has PID 1 in that namespace as well as its PID in the parent process’ namespace. See below for an example.
A network namespace has an independent network stack: its own private routing table, set of IP addresses, socket listing, connection tracking table, firewall, and other network‑related resources.
A mount namespace has an independent list of mount points seen by the processes in the namespace. This means that you can mount and unmount filesystems in a mount namespace without affecting the host filesystem.
An interprocess communication (IPC) namespace has its own IPC resources, for example POSIX message queues.
A UNIX Time‑Sharing (UTS) namespace allows a single system to appear to have different host and domain names to different processes.
the container are fast isolated environment , we will focus on this part many things are involved and my main goal is to Demystifying Containers
assuming that you are on a linux machine (try Power shell Ubuntu image if you are on Windows :-)
این دستور را اجرا کنید: شناسه
host-machine $ id
uid=1000(mohamed) gid=1000(mohamed) groups=1000(mohamed) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c.1023
فرمان UNSHARE
اکنون دستور UNSHARE زیر را اجرا می کنم تا یک فضای نام جدید را با کاربر خود و نام های نام PID ایجاد کنم. من کاربر root را به فضای نام جدید (به عبارت دیگر ، من در فضای نام جدید امتیاز اصلی دارم) ، یک سیستم فایل جدید را سوار می کنم و روند خود را (در این حالت ، bash) در فضای نام تازه ایجاد شده چنگال می کنم.
Unshare-USER–PID–MAP-ROOT-USER–MOUNT-PROC-FORK BASH
تبریک می گویم ، شما در فضای نام منزوی و برخی از نحوه کار هستید
PID جدا شده در همان سیستم فایل و همان شبکه ، نقطه ورود /سطل /سطل /bash شما
دستور PS -EF نشان می دهد که دو فرآیند در حال اجرا هستند -Bash و فرمان PS خود -و دستور ID تأیید می کند که من در فضای نام جدید ریشه دارم (که توسط سریع دستور تغییر یافته نیز نشان داده شده است):
root # ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 14:46 pts/0 00:00:00 bash
root 15 1 0 14:46 pts/0 00:00:00 ps -ef
root # id
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c.1023
نام های نام و ظروف
نام های نام یکی از فناوری هایی است که ظروف در آن ساخته شده است و برای اجرای تفکیک منابع استفاده می شود. ما نشان داده ایم که چگونه می توان نام های نام را به صورت دستی ایجاد کرد ، اما زمان های کانتینر مانند Docker ، RKT ، Podman ، Runc ، Containerd و بسیاری دیگر از فناوری های کانتینر
یکی از منحصر به فرد ترین پروژه ها https://katacontainers.io/ آنها ادعا می کنند که بین کانتینر و VM مخلوط می شوند.
cGroups چیست؟
CGroups یا گروه های کنترل یک ویژگی هسته لینوکس هستند که مدیریت و محدودیت منابع سیستم مانند CPU ، حافظه و پهنای باند شبکه را از جمله دیگر امکان پذیر می کند. ما می توانیم از گروههای مختلف برای تعیین محدودیت در این منابع استفاده کنیم و آنها را در بین گروه های مختلف فرآیندها توزیع کنیم.
CGroups دارای یک ساختار سلسله مراتبی با ریشه و کودک است که هر یک با محدودیت منابع تعیین شده توسط کنترل کننده ها – به عنوان مثال ، یک کنترل کننده CPU برای زمان CPU یا یک کنترلر حافظه برای حافظه.
ما می توانیم از گروههای مختلف برای اهداف مختلف استفاده کنیم ، مانند کنترل استفاده از منابع در یک محیط چند مستاجر ، ارائه کیفیت خدمات (QoS) و در حال اجرا.
cGroups ویژگی های زیر را ارائه می دهد:
Resource limits — You can configure a cgroup to limit how much of a particular resource (memory or CPU, for example) a process can use.
Prioritization — You can control how much of a resource (CPU, disk, or network) a process can use compared to processes in another cgroup when there is resource contention.
Accounting — Resource limits are monitored and reported at the cgroup level.
Control — You can change the status (frozen, stopped, or restarted) of all processes in a cgroup with a single command.
ایجاد یک گروه
دستور زیر یک cGroup V1 (می توانید با فرمت pathname) به نام FOO ایجاد کنید و حد حافظه را برای 50،000،000 بایت (50 مگابایت) تنظیم می کند.
root # mkdir -p /sys/fs/cgroup/memory/foo
root # echo 50000000 > /sys/fs/cgroup/memory/foo/memory.limit_in_bytes
اکنون می توانم یک فرآیند را به CGroup اختصاص دهم ، بنابراین محدودیت حافظه CGroup را بر آن تحمیل می کنم. من یک اسکریپت پوسته به نام test.sh را نوشتم ، که ابزار تست CGROUP را به صفحه چاپ می کند ، و سپس منتظر انجام هیچ کاری نیست. برای اهداف من ، این فرایندی است که تا زمانی که جلوی آن را بگیرم ادامه می یابد.
من Test.sh را در پس زمینه شروع می کنم و PID آن به عنوان 2428 گزارش شده است. اسکریپت خروجی خود را تولید می کند و سپس با لوله کشی PID آن در پرونده CGROUP/SYS/FS/CGROUP/MEAMS/FOO/CGROUP ، فرآیند را به CGroup اختصاص می دهم. .procs
root # ./test.sh &
[1] 2428
root # cgroup testing tool
root # echo 2428 > /sys/fs/cgroup/memory/foo/cgroup.procs
برای تأیید اینكه فرایند من در واقع مشمول محدودیت حافظه است كه من برای CGroup FOO تعریف كرده ام ، دستور PS زیر را اجرا می كنم. پرچم CGROUP -O گروه های CGROUP را که فرآیند مشخص شده (2428) به آن تعلق دارد ، نشان می دهد. خروجی تأیید می کند که CGROUP حافظه آن FOO است.
root # ps -o cgroup 2428
CGROUP
12:pids:/user.slice/user-0.slice/\
session-13.scope,10:devices:/user.slice,6:memory:/foo,...
به طور پیش فرض ، سیستم عامل هنگامی که بیش از حد منابع تعریف شده توسط CGROUP خود باشد ، فرایندی را خاتمه می دهد.
و این مقدار عادلانه از اطلاعات در مورد فضای نام و cGroup
شما می توانید Doc Full Doc را در مورد آن توسط Scott Van Kalken از F5 بخوانید
در این لینک ، همچنین در این پست ظروف تغییر شکل 101 و این یکی روی اکوسیستم Docker “یک مقدمه دوستانه مبتدی برای ظروف ، VMS و Docker” تمرکز دارد.
قسمت 1: کروت
من از مکانهای نام استفاده نمی کنم ، “در این قسمت”
این ممکن است تعجب کند اما من به انزوا دست می یابم ، ما از یک ابزار ساده یونیکس از Chroot استفاده خواهیم کرد
Chroot ، کوتاه برای “تغییر ریشه” ، یک تماس سیستم یونیکس است که فهرست اصلی یک فرآیند را به یک مسیر مشخص تغییر می دهد ، به طور موثری یک سیستم فایل ریشه جدید برای فرآیند و فرزندان خود ایجاد می کند. این می تواند ابزاری قدرتمند برای ایجاد محیط های جدا شده یا “زندان های کروت” باشد.
چگونه Chroot کار می کند:
Setting a New Root Directory: When you execute the chroot system call or the chroot command in the shell, it changes the root directory for the process and its children. The new root directory becomes the / (root) directory for that process, isolating it from the actual root directory of the host system.
Isolation: After the chroot operation, the process and its children can only access files and directories within the new root directory. They cannot access files outside this new root, providing a level of isolation and containment.
موارد استفاده:
System Recovery: chroot is commonly used in system recovery scenarios. If your system becomes unbootable or experiences issues, you can boot from a live CD/USB, chroot into the broken system, and make necessary repairs without affecting the rest of the host system.
Environment Isolation: Developers and system administrators may use chroot to create isolated environments for testing or building software. This is especially common in scenarios where different versions of libraries or dependencies are required.
Security: Although chroot provides some level of isolation, it's not foolproof in terms of security. It was not designed as a security feature and should not be solely relied upon for containing malicious processes. Modern containerization technologies, like Docker, utilize more advanced mechanisms, such as Linux namespaces and cgroups, to provide stronger isolation.
مثال:
مثال زیر را در نظر بگیرید:
mkdir mychroot
cp -r /bin /lib /lib64 /usr /mychroot
chroot /mychroot /bin/bash
در این مثال:
We create a directory called mychroot and copy essential binaries and libraries into it.
We use chroot to change the root directory to /mychroot.
After the chroot command, executing /bin/bash will run a Bash shell within the isolated environment.
به خاطر داشته باشید که Chroot به خودی خود انزوای کاملی را ارائه نمی دهد. این ماده اغلب در رابطه با سایر ابزارها و تکنیک ها برای ایجاد محیط های ایمن تر و قوی تر کانتینر استفاده می شود.
سیستم فایل اوبونتو را آماده کنید
اکنون این مورد نهایی را که قبل از شروع سیستم فایل به آن نیاز دارید ، نهایی خواهید کرد.
ما برای بارگیری سیستم فایل اوبونتو از Docker استفاده خواهیم کرد
برای بارگیری آن ، در ریشه پروژه خود فقط به Docker نیاز خواهید داشت
$ docker run -d --rm --name ubuntu_fs ubuntu:20.04 sleep 1000
$ mkdir -p ./ubuntu_fs
$ docker cp ubuntu_fs:/ ./ubuntu_fs
$ docker stop ubuntu_fs
اکنون Ubuntu_fs را در داخل پروژه خود ، داخل بسته اصلی شما قرار داده ایم
package main
import (
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strconv"
"syscall"
"strings"
"fmt"
"github.com/vishvananda/netns"
)
func main() {
switch os.Args[1] {
case "run":
run(os.Args[2:]...)
case "child":
child(os.Args[2:]...)
default:
log.Fatal("Unknown command. Use run , like `run /bin/bash` or `run echo hello`")
}
}
func run(command ...string) {
log.Println("Executing", command, "from run")
cmd := exec.Command("/proc/self/exe", append([]string{"child"}, command[0:]...)...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// Cloneflags is only available in Linux
// CLONE_NEWUTS namespace isolates hostname
// CLONE_NEWPID namespace isolates processes
// CLONE_NEWNS namespace isolates mounts
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS ,
Unshareflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWNET,
}
// Run child using namespaces. The command provided will be executed inside that.
must(cmd.Run())
}
func child(command ...string) {
// Create cgroup
cg()
cmd := exec.Command(command[0], command[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
must(syscall.Sethostname([]byte("container")))
must(syscall.Chroot("./ubuntu_fs"))
// Change directory after chroot
must(os.Chdir("https://dev.to/"))
// Mount /proc inside container so that `ps` command works
must(syscall.Mount("proc", "proc", "proc", 0, ""))
// Mount a temporary filesystem
if _, err := os.Stat("mytemp"); os.IsNotExist(err) {
must(os.Mkdir("mytemp", os.ModePerm))
}
must(syscall.Mount("something", "mytemp", "tmpfs", 0, ""))
must(cmd.Run())
// Cleanup mount
must(syscall.Unmount("proc", 0))
must(syscall.Unmount("mytemp", 0))
}
func cg() {
// cgroup location in Ubuntu
cgroups := "/sys/fs/cgroup/"
pids := filepath.Join(cgroups, "pids")
containers_mini := filepath.Join(pids, "containers_mini")
os.Mkdir(containers_mini, 0755)
// Limit to max 20 pids
must(ioutil.WriteFile(filepath.Join(containers_mini, "pids.max"), []byte("20"), 0700))
// Cleanup cgroup when it is not being used
must(ioutil.WriteFile(filepath.Join(containers_mini, "notify_on_release"), []byte("1"), 0700))
pid := strconv.Itoa(os.Getpid())
// Apply this and any child process in this cgroup
must(ioutil.WriteFile(filepath.Join(containers_mini, "cgroup.procs"), []byte(pid), 0700))
}
func must(err error) {
if err != nil {
log.Printf("Error: %v\n", err)
panic(err)
}
}
این کد معرفی شده توسط لیز رایس
https://youtu.be/utf-a4rodh8؟si=uluze8e5n7n17dh9
درک کد
1 عملکرد اصلی
عملکرد اصلی به عنوان نقطه ورود برنامه عمل می کند. از آرگومان های خط فرمان برای تعیین اینکه آیا یک ظرف جدید را اجرا می کند یا به عنوان یک فرآیند کودک در یک ظرف موجود عمل می کند ، استفاده می کند.
func main() {
switch os.Args[1] {
case "run":
run(os.Args[2:]...)
case "child":
child(os.Args[2:]...)
default:
log.Fatal("Unknown command. Use run , like `run /bin/bash` or `run echo hello`")
}
}
- تابع اجرا
عملکرد RUN محیط کانتینر را تنظیم می کند و یک دستور مشخص را در داخل آن اجرا می کند.
func run(command ...string) {
log.Println("Executing", command, "from run")
cmd := exec.Command("/proc/self/exe", append([]string{"child"}, command[0:]...)...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS,
Unshareflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWNET,
}
must(cmd.Run())
}
this command cmd := exec.Command(“/proc/self/exe”, append([]string{“child”}, command[0:]…)…)
make sure that it’s append all command to same process
The Cloneflags specify the namespaces to be isolated (UTS, PID, and mount namespaces).
The Unshareflags further isolate the network namespace.
The cmd.Run() method runs the provided command within the created container.
- عملکرد کودک
عملکرد کودک وظیفه تنظیم سیستم فایل کانتینر و اجرای دستور مشخص شده در داخل آن را بر عهده دارد.
func child(command ...string) {
// ...
cg()
must(syscall.Sethostname([]byte("container")))
must(syscall.Chroot("./ubuntu_fs"))
must(os.Chdir("https://dev.to/"))
must(syscall.Mount("proc", "proc", "proc", 0, ""))
must(syscall.Mount("something", "mytemp", "tmpfs", 0, ""))
must(cmd.Run())
must(syscall.Unmount("proc", 0))
must(syscall.Unmount("mytemp", 0))
}
The cg function sets up a control group (cgroup) to limit resource usage for the container.
Sethostname sets the hostname inside the container.
Chroot changes the root directory for the container.
Mount is used to mount essential filesystems like /proc and a temporary filesystem.
Finally, the command is executed within the container.
- گروه های کنترل (CGROUP)
عملکرد CG یک گروه را برای ظرف ایجاد و پیکربندی می کند و تعداد فرآیندها را محدود می کند.
func cg() {
// cgroup location in Ubuntu
cgroups := "/sys/fs/cgroup/"
pids := filepath.Join(cgroups, "pids")
containers_mini := filepath.Join(pids, "containers_mini")
os.Mkdir(containers_mini, 0755)
// Limit to max 20 pids
must(ioutil.WriteFile(filepath.Join(containers_mini, "pids.max"), []byte("20"), 0700))
// Cleanup cgroup when it is not being used
must(ioutil.WriteFile(filepath.Join(containers_mini, "notify_on_release"), []byte("1"), 0700))
pid := strconv.Itoa(os.Getpid())
// Apply this and any child process in this cgroup
must(ioutil.WriteFile(filepath.Join(containers_mini, "cgroup.procs"), []byte(pid), 0700))
}
Cgroups are used to control and limit resource usage for processes.
In this example, the cgroup limits the maximum number of processes to 20.
- رسیدگی به خطا
عملکرد MUST یک تابع ابزار ساده برای رسیدگی به خطاها است.
func must(err error) {
if err != nil {
log.Printf("Error: %v\n", err)
panic(err)
}
}
در صورت بروز خطایی ، ورود به سیستم و برنامه خاتمه می یابد.
ساخت و دویدن ظرف
برای اجرای کانتینر حداقل ، این مراحل را دنبال کنید:
Build the executable: go build -o mycontainer main.go
Create a filesystem directory with an Ubuntu root filesystem, e.g., ubuntu_fs.
Run the container: sudo ./mycontainer run /bin/bash
remember you need to run it as sudo
your entry point is /bin/bash
اکنون شما در کانتینر حداقل خود هستید ، و اکنون درک عمیقی دارید ، ممکن است اگر من در آینده وقت بیشتری داشته باشم ، لایه انزوا را در شبکه اضافه می کنم ، ما می توانید این کار را انجام دهید ، از شما برای وقت شما متشکرم که کمک کردم هر کسی
read this will help you more
namespace & golang a series of article explains namespace with go examples
“Creating Network Stacks and Connecting with the Internet” by “Shrikanta Mazumder”
https://songrgg.github.io/programming/linux-namespace-part01-uts-pid/
در قسمت بعدی ما یک لایه شبکه ایجاد خواهیم کرد که به کانتینر ما اترنت مجازی در زیر مجموعه جدا شده که از Bridge Host به عنوان Gateway استفاده می کنند ، می دهد. به زودی می بینمت
قسمت 2
شما می توانید من را در LinkedIn پیدا کنید
https://www.linkedin.com/in/mohamed-elkerwash/