اگر در حال خواندن این مطلب هستید، احتمالاً چیزی در مورد Kubernetes شنیده اید (و اگر نه، چگونه به اینجا رسیدید؟) اما دقیقاً Kubernetes چیست؟ این "ارکستراسیون کانتینرهای صنعتی"? یا "سیستم عامل Cloud-Native"? این اصلا به چه معناست؟
راستش من 100% مطمئن نیستم. اما من فکر میکنم جالب است که درونیها را کاوش کنیم و ببینیم واقعاً در Kubernetes تحت لایههای متعدد انتزاع آن چه میگذرد. بنابراین فقط برای سرگرمی، بیایید نگاهی بیاندازیم به اینکه یک "خوشه Kubernetes" مینیمال در واقع چگونه به نظر می رسد. (این بسیار ساده تر از این خواهد بود Kubernetes راه سخت.)
من فرض میکنم شما دانش اولیه Kubernetes، Linux، و کانتینرها را دارید. همه چیزهایی که ما در اینجا در مورد آن صحبت می کنیم فقط برای اهداف تحقیقاتی/آموزشی است، هیچ کدام از آنها را وارد تولید نکنید!
مرور
Kubernetes شامل اجزای بسیاری است. مطابق با ویکیپدیا، معماری به شکل زیر است:
حداقل هشت مؤلفه در اینجا نشان داده شده است، اما ما بیشتر آنها را نادیده می گیریم. من می خواهم بگویم که حداقل چیزی که می توان به طور منطقی Kubernetes نامید از سه جزء اصلی تشکیل شده است:
کوبلت
kube-apiserver (که به etcd بستگی دارد - پایگاه داده آن)
زمان اجرا کانتینر (در این مورد Docker)
بیایید ببینیم اسناد در مورد هر یک از آنها چه می گوید (rus., انگلیسی.). در ابتدا کوبلت:
یک عامل در حال اجرا بر روی هر گره در خوشه. این اطمینان حاصل می کند که ظروف در غلاف در حال اجرا هستند.
به اندازه کافی ساده به نظر می رسد. چه در مورد زمان اجرا کانتینر (زمان اجرای کانتینر)؟
Container Runtime برنامه ای است که برای اجرای کانتینرها طراحی شده است.
بسیار آموزنده اما اگر با Docker آشنا هستید، باید یک ایده کلی از کاری که انجام می دهد داشته باشید. (جزئیات تفکیک مسئولیت ها بین زمان اجرای کانتینر و کوبلت در واقع کاملاً ظریف است و من در اینجا به آنها نمی پردازم.)
И سرور API?
سرور API جزء کنترل پنل Kubernetes است که API Kubernetes را در معرض دید قرار می دهد. سرور API سمت کلاینت کنترل پنل Kubernetes است
هرکسی که تا به حال کاری با Kubernetes انجام داده است، باید مستقیماً یا از طریق kubectl با API تعامل داشته باشد. این قلب چیزی است که Kubernetes Kubernetes را می سازد - مغزی که کوه های YAML را که همه ما می شناسیم و دوست داریم (؟) به زیرساخت های کاری تبدیل می کند. بدیهی است که API باید در پیکربندی حداقلی ما وجود داشته باشد.
پیش شرط ها
ماشین مجازی یا فیزیکی لینوکس با دسترسی ریشه (من از اوبونتو 18.04 در ماشین مجازی استفاده می کنم).
و این همه!
نصب خسته کننده
ما باید Docker را روی دستگاهی که استفاده خواهیم کرد نصب کنیم. (من قصد ندارم به جزئیات در مورد نحوه کار داکر و کانتینرها بپردازم؛ اگر علاقه مند هستید، مقالات فوق العاده). بیایید فقط آن را با آن نصب کنیم apt:
پس از آن، باید باینری های Kubernetes را بدست آوریم. در واقع، برای راه اندازی اولیه "خوشه" ما فقط نیاز داریم kubelet، از آنجایی که برای اجرای سایر اجزای سرور می توانیم استفاده کنیم kubelet. برای تعامل با خوشه خود پس از اجرا، از آن نیز استفاده خواهیم کرد kubectl.
kubelet باید به صورت روت اجرا شود. کاملاً منطقی است، زیرا او باید کل گره را مدیریت کند. بیایید به پارامترهای آن نگاه کنیم:
$ ./kubelet -h
<слишком много строк, чтобы разместить здесь>
$ ./kubelet -h | wc -l
284
وای، گزینه های زیادی! خوشبختانه ما فقط به چند تا از آنها نیاز داریم. در اینجا یکی از پارامترهایی است که ما به آن علاقه داریم:
--pod-manifest-path string
مسیری به دایرکتوری حاوی فایلهایی برای پادهای ثابت، یا مسیری به فایلی که غلافهای استاتیک را توصیف میکند. فایل هایی که با نقطه شروع می شوند نادیده گرفته می شوند. (منسوخ شده: این گزینه باید در فایل پیکربندی ارسال شده به Kubelet از طریق گزینه --config تنظیم شود. برای اطلاعات بیشتر، رجوع کنید به kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)
این گزینه به ما امکان اجرا را می دهد غلاف های ساکن - پادهایی که از طریق Kubernetes API مدیریت نمی شوند. غلاف های استاتیک به ندرت استفاده می شوند، اما برای بالا بردن سریع یک خوشه بسیار راحت هستند و این دقیقا همان چیزی است که ما به آن نیاز داریم. ما این هشدار بزرگ را نادیده می گیریم (باز هم، این را در تولید اجرا نکنید!) و ببینیم که آیا می توانیم پاد را راه اندازی کنیم.
ابتدا یک دایرکتوری برای پادهای ثابت ایجاد می کنیم و اجرا می کنیم kubelet:
kubelet شروع به نوشتن چند هشدار می کند و به نظر می رسد که هیچ اتفاقی نمی افتد. اما این درست نیست! بیایید به Docker نگاه کنیم:
$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8c8a35e26663 busybox "echo 'hello world!'" 36 seconds ago Exited (0) 36 seconds ago k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
68f670c3c85f k8s.gcr.io/pause:3.2 "/pause" 2 minutes ago Up 2 minutes k8s_POD_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_0
$ sudo docker logs k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
hello world!
kubelet من مانیفست پاد را خواندم و به داکر دستور دادم تا طبق مشخصات ما چند کانتینر راه اندازی کند. (اگر در مورد ظرف "مکث" تعجب می کنید، این یک هک Kubernetes است - ببینید این وبلاگ.) Kubelet کانتینر ما را راه اندازی خواهد کرد busybox با دستور مشخص شده و به طور نامحدود آن را مجدداً راه اندازی می کند تا زمانی که پاد استاتیک حذف شود.
به خودت تبریک بگو ما به تازگی به یکی از گیج کننده ترین راه ها برای خروجی متن به ترمینال رسیده ایم!
etcd را راه اندازی کنید
هدف نهایی ما اجرای Kubernetes API است، اما برای انجام این کار ابتدا باید اجرا کنیم etcd. بیایید با قرار دادن تنظیمات آن در دایرکتوری pods یک خوشه etcd حداقلی را شروع کنیم (به عنوان مثال، pods/etcd.yaml):
اگر تا به حال با Kubernetes کار کرده اید، این فایل های YAML باید برای شما آشنا باشند. در اینجا تنها دو نکته قابل توجه است:
ما پوشه میزبان را نصب کرده ایم /var/lib/etcd در پاد به طوری که دادههای etcd پس از راهاندازی مجدد حفظ شوند (اگر این کار انجام نشود، هر بار که غلاف راهاندازی مجدد میشود، وضعیت کلاستر پاک میشود، که حتی برای نصب حداقل Kubernetes خوب نخواهد بود).
نصب کرده ایم hostNetwork: true. این تنظیم، بدون تعجب، etcd را برای استفاده از شبکه میزبان به جای شبکه داخلی pod پیکربندی میکند (این کار باعث میشود که سرور API بتواند خوشه etcd را پیدا کند).
یک بررسی ساده نشان می دهد که etcd واقعاً روی لوکال هاست اجرا می شود و داده ها را روی دیسک ذخیره می کند:
$ curl localhost:2379/version
{"etcdserver":"3.4.3","etcdcluster":"3.4.0"}
$ sudo tree /var/lib/etcd/
/var/lib/etcd/
└── member
├── snap
│ └── db
└── wal
├── 0.tmp
└── 0000000000000000-0000000000000000.wal
راه اندازی سرور API
اجرای یک سرور API Kubernetes حتی ساده تر است. تنها پارامتری که باید پاس شود این است --etcd-servers، آنچه را که انتظار دارید انجام می دهد:
این فایل YAML را در دایرکتوری قرار دهید podsو سرور API شروع به کار خواهد کرد. چک کردن با curl نشان می دهد که API Kubernetes در پورت 8080 با دسترسی کاملاً باز گوش می دهد - بدون نیاز به احراز هویت!
(باز هم، این را در مرحله تولید اجرا نکنید! من کمی تعجب کردم که تنظیمات پیش فرض آنقدر ناامن است. اما حدس می زنم این کار برای آسان کردن توسعه و آزمایش باشد.)
و، شگفتی دلپذیر، kubectl بدون هیچ تنظیمات اضافی کار می کند!
$ ./kubectl version
Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:47:41Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:39:24Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
$ ./kubectl get pod
No resources found in default namespace.
مشکل
اما اگر کمی عمیقتر شوید، به نظر میرسد چیزی اشتباه پیش میرود:
$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.
غلاف های ثابتی که ایجاد کردیم از بین رفته اند! در واقع، گره kubelet ما اصلاً کشف نشده است:
$ ./kubectl get nodes
No resources found in default namespace.
موضوع چیه؟ اگر چند پاراگراف قبل را به خاطر دارید، ما کوبلت را با مجموعه ای بسیار ساده از پارامترهای خط فرمان شروع کردیم، بنابراین kubelet نمی داند چگونه با سرور API تماس بگیرد و وضعیت خود را به آن اطلاع دهد. پس از مطالعه اسناد، پرچم مربوطه را پیدا می کنیم:
--kubeconfig string
مسیر فایل kubeconfig، که نحوه اتصال به سرور API را مشخص می کند. دسترسی --kubeconfig حالت سرور API را فعال می کند، خیر --kubeconfig حالت آفلاین را فعال می کند.
در تمام این مدت، بدون اینکه بدانیم، کوبلت را در حالت آفلاین اجرا می کردیم. (اگر ما بچهدار بودیم، میتوانستیم یک کوبلت مستقل را بهعنوان «حداقل کوبرنتهای قابل دوام» در نظر بگیریم، اما این بسیار کسلکننده خواهد بود). برای اینکه پیکربندی "واقعی" کار کند، باید فایل kubeconfig را به kubelet منتقل کنیم تا بداند چگونه با سرور API صحبت کند. خوشبختانه این بسیار ساده است (زیرا ما هیچ مشکلی با احراز هویت یا گواهینامه نداریم):
(به هر حال، اگر سعی کنید از طریق curl به API دسترسی پیدا کنید، زمانی که کوبلت در حال اجرا نیست، متوجه خواهید شد که هنوز در حال اجرا است! Kubelet مانند Docker یک "والد" پادهای خود نیست، بلکه بیشتر شبیه یک "کنترل" است. ظروف تحت مدیریت یک کوبلت تا زمانی که کوبلت آنها را متوقف کند به کار خود ادامه خواهند داد.)
در چند دقیقه kubectl باید غلاف ها و گره ها را همانطور که انتظار داریم به ما نشان دهد:
$ ./kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
default hello-mink8s 0/1 CrashLoopBackOff 261 21h
kube-system etcd-mink8s 1/1 Running 0 21h
kube-system kube-apiserver-mink8s 1/1 Running 0 21h
$ ./kubectl get nodes -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
mink8s Ready <none> 21h v1.18.5 10.70.10.228 <none> Ubuntu 18.04.4 LTS 4.15.0-109-generic docker://19.3.6
بیایید این بار واقعاً به خودمان تبریک بگوییم (می دانم که قبلاً به خودمان تبریک گفته ام) - ما یک "خوشه" کوچک Kubernetes داریم که با یک API کاملاً کاربردی اجرا می شود!
زیر راه اندازی می کنیم
حال بیایید ببینیم که API چه توانایی هایی دارد. بیایید با nginx pod شروع کنیم:
$ ./kubectl apply -f nginx.yaml
Error from server (Forbidden): error when creating "nginx.yaml": pods "nginx" is
forbidden: error looking up service account default/default: serviceaccount
"default" not found
$ ./kubectl get serviceaccounts
No resources found in default namespace.
در اینجا می بینیم که محیط Kubernetes ما چقدر ناقص است - ما هیچ حساب کاربری برای خدمات نداریم. بیایید با ایجاد دستی یک حساب سرویس دوباره تلاش کنیم و ببینیم چه اتفاقی میافتد:
$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: default
EOS
serviceaccount/default created
$ ./kubectl apply -f nginx.yaml
Error from server (ServerTimeout): error when creating "nginx.yaml": No API
token found for service account "default", retry after the token is
automatically created and added to the service account
حتی زمانی که حساب سرویس را به صورت دستی ایجاد کردیم، رمز احراز هویت ایجاد نمیشود. همانطور که ما به آزمایش با "خوشه" حداقلی خود ادامه می دهیم، متوجه خواهیم شد که بسیاری از چیزهای مفیدی که معمولاً به طور خودکار اتفاق می افتد از دست خواهند رفت. سرور Kubernetes API کاملاً مینیمالیستی است، با بیشتر پیکربندیهای سنگین و خودکار در کنترلرهای مختلف و کارهای پسزمینه که هنوز اجرا نشدهاند.
با تنظیم گزینه می توانیم این مشکل را حل کنیم automountServiceAccountToken برای حساب سرویس (زیرا به هر حال مجبور نیستیم از آن استفاده کنیم):
$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: default
automountServiceAccountToken: false
EOS
serviceaccount/default configured
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx 0/1 Pending 0 13m
بالاخره غلاف ظاهر شد! اما در واقع شروع نمی شود زیرا ما نداریم برنامه ریز (زمانبندی) یکی دیگر از اجزای مهم Kubernetes است. باز هم می بینیم که Kubernetes API به طور شگفت انگیزی "گنگ" است - وقتی یک Pod را در API ایجاد می کنید، آن را ثبت می کند، اما سعی نمی کند بفهمد آن را روی چه گره ای اجرا کند.
در واقع برای اجرای پاد نیازی به زمانبندی ندارید. می توانید به صورت دستی یک گره به مانیفست در پارامتر اضافه کنید nodeName:
(جایگزین کردن mink8s به نام گره.) پس از حذف و اعمال، می بینیم که nginx شروع شده است و به آدرس IP داخلی گوش می دهد:
$ ./kubectl delete pod nginx
pod "nginx" deleted
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 30s 172.17.0.2 mink8s <none> <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
برای اطمینان از اینکه شبکه بین پادها به درستی کار می کند، می توانیم curl را از یک پاد دیگر اجرا کنیم:
$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: curl
spec:
containers:
- image: curlimages/curl
name: curl
command: ["curl", "172.17.0.2"]
nodeName: mink8s
EOS
pod/curl created
$ ./kubectl logs curl | head -6
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
بسیار جالب است که در این محیط کاوش کنید و ببینید چه چیزی کار می کند و چه چیزی نه. متوجه شدم که ConfigMap و Secret همانطور که انتظار می رود کار می کنند، اما Service و Deployment اینطور نیست.
!
این پست در حال طولانی شدن است، بنابراین من می خواهم پیروزی را اعلام کنم و بگویم که این یک پیکربندی قابل اجرا است که می توان آن را "Kubernetes" نامید. به طور خلاصه: چهار باینری، پنج پارامتر خط فرمان و "فقط" 45 خط YAML (نه تا حد زیادی طبق استانداردهای Kubernetes) و ما چندین چیز داریم که کار می کنند:
پادها با استفاده از API معمولی Kubernetes مدیریت می شوند (با چند هک)
می توانید تصاویر کانتینر عمومی را آپلود و مدیریت کنید
پادها زنده می مانند و به طور خودکار راه اندازی مجدد می شوند
شبکه سازی بین پادها در همان گره بسیار خوب عمل می کند
ConfigMap، Secret و نصب ذخیره سازی ساده همانطور که انتظار می رود کار می کنند
اما بسیاری از چیزهایی که Kubernetes را واقعاً مفید میسازد هنوز گم شدهاند، مانند:
زمانبندی پاد
احراز هویت / مجوز
گره های متعدد
شبکه خدمات
DNS داخلی خوشه ای
کنترلکنندههای حسابهای سرویس، استقرار، ادغام با ارائهدهندگان ابری و بسیاری از چیزهای دیگری که Kubernetes به ارمغان میآورد.
پس در واقع چه چیزی به دست آوردیم؟ Kubernetes API که به تنهایی اجرا می شود، در واقع فقط یک پلتفرم برای آن است اتوماسیون کانتینری. کار زیادی انجام نمی دهد - این کار برای کنترلرها و اپراتورهای مختلف با استفاده از API است - اما یک محیط سازگار برای اتوماسیون فراهم می کند.