تقريبا. العابرة. : شارك مؤلف المقال - إركان إيرول ، وهو مهندس من SAP - في دراسته للآليات kubectl exec
لفريق kubectl exec
، وهو مألوف للغاية لكل من يعمل مع Kubernetes. وهو يرافق الخوارزمية بأكملها مع قوائم برمز مصدر Kubernetes (والمشاريع ذات الصلة) ، والتي تتيح لك فهم الموضوع بالعمق المطلوب.
ذات يوم جمعة ، جاء زميل لي وسأل عن كيفية تنفيذ أمر في جراب باستخدام
client-go . لم أستطع الإجابة عليه وأدركت فجأة أنني لا أعرف أي شيء عن آلية عمل
kubectl exec
. نعم ، كان لدي بعض الأفكار حول أجهزتها ، لكنني لم أكن متأكداً 100 ٪ من صحتها ، وبالتالي قررت معالجة هذه المشكلة. بعد أن درست المدونات والوثائق والكود المصدري ، تعلمت الكثير من الأشياء الجديدة ، وفي هذا المقال أود أن أشارك في اكتشافاتي وفهماتي. إذا كان هناك خطأ ما ، فيرجى الاتصال بي على
Twitter .
تدريب
لإنشاء كتلة على MacBook ، قمت
باستنساخ نظام ecomm-integration-ballerina / kubernetes-cluster . ثم قام بتصحيح عناوين IP للعقد في تكوين kubelet ، حيث أن الإعدادات الافتراضية لم تسمح
kubectl exec
. يمكنك قراءة المزيد عن السبب الرئيسي لهذا
هنا .
- أي سيارة = بلدي ماك بوك
- ماجستير IP = 192.168.205.10
- عنوان IP للعامل = 192.168.205.11
- منفذ خادم API = 6443
المكونات

- kubectl exec process : عندما ننفذ "kubectl exec ..." ، تبدأ العملية. يمكنك القيام بذلك على أي جهاز مع الوصول إلى خادم K8s API. تقريبا. trans.: علاوة على ذلك في قوائم وحدة التحكم ، يستخدم المؤلف التعليق "أي جهاز" ، مما يعني أنه يمكن تنفيذ الأوامر اللاحقة على أي من هذه الأجهزة مع الوصول إلى Kubernetes.
- خادم api : مكون على الرئيسي يوفر الوصول إلى Kubernetes API. هذه هي الواجهة الأمامية لطائرة التحكم في Kubernetes.
- kubelet : عامل يعمل على كل عقدة في الكتلة. ويوفر الحاويات في pod'e.
- وقت تشغيل الحاوية ( وقت تشغيل الحاوية ): برنامج مسؤول عن تشغيل الحاويات. أمثلة: Docker، CRI-O، containerd ...
- kernel : OS kernel على عقدة العمل ؛ المسؤول عن إدارة العمليات.
- الحاوية المستهدفة : الحاوية التي تعد جزءًا من الحافظة وتعمل على أحد عقد العمل.
ما اكتشفته
1. نشاط جانب العميل
إنشاء جراب في مساحة الاسم
default
:
// any machine $ kubectl run exec-test-nginx --image=nginx
ثم ننفذ الأمر exec وننتظر 5000 ثانية لمزيد من الملاحظات:
// any machine $ kubectl exec -it exec-test-nginx-6558988d5-fgxgg -- sh
تظهر عملية kubectl (مع معرف المنتج = 8507 في حالتنا):
// any machine $ ps -ef |grep kubectl 501 8507 8409 0 7:19PM ttys000 0:00.13 kubectl exec -it exec-test-nginx-6558988d5-fgxgg -- sh
إذا تحققنا من نشاط الشبكة للعملية ، وجدنا أن لديها اتصالات بخادم api (192.168.205.10.6443):
// any machine $ netstat -atnv |grep 8507 tcp4 0 0 192.168.205.1.51673 192.168.205.10.6443 ESTABLISHED 131072 131768 8507 0 0x0102 0x00000020 tcp4 0 0 192.168.205.1.51672 192.168.205.10.6443 ESTABLISHED 131072 131768 8507 0 0x0102 0x00000028
لنلقِ نظرة على الكود يقوم Kubectl بإنشاء طلب POST باستخدام مصدر فرعي exec ويرسل طلب REST:
req := restClient.Post(). Resource("pods"). Name(pod.Name). Namespace(pod.Namespace). SubResource("exec") req.VersionedParams(&corev1.PodExecOptions{ Container: containerName, Command: p.Command, Stdin: p.Stdin, Stdout: p.Out != nil, Stderr: p.ErrOut != nil, TTY: t.Raw, }, scheme.ParameterCodec) return p.Executor.Execute("POST", req.URL(), p.Config, p.In, p.Out, p.ErrOut, t.Raw, sizeQueue)
( kubectl / pkg / cmd / exec / exec.go )
2. النشاط على جانب العقدة الرئيسية
يمكننا أيضًا مراقبة الطلب من جانب خادم api:
handler.go:143] kube-apiserver: POST "/api/v1/namespaces/default/pods/exec-test-nginx-6558988d5-fgxgg/exec" satisfied by gorestful with webservice /api/v1 upgradeaware.go:261] Connecting to backend proxy (intercepting redirects) https://192.168.205.11:10250/exec/default/exec-test-nginx-6558988d5-fgxgg/exec-test-nginx?command=sh&input=1&output=1&tty=1 Headers: map[Connection:[Upgrade] Content-Length:[0] Upgrade:[SPDY/3.1] User-Agent:[kubectl/v1.12.10 (darwin/amd64) kubernetes/e3c1340] X-Forwarded-For:[192.168.205.1] X-Stream-Protocol-Version:[v4.channel.k8s.io v3.channel.k8s.io v2.channel.k8s.io channel.k8s.io]]
لاحظ أن طلب HTTP يتضمن طلب تغيير البروتوكول. يتيح لك SPDY مضاعفة التدفقات الفردية stdin / stdout / stderr / spdy-error من خلال اتصال TCP واحد.خادم API يتلقى الطلب ويحوله إلى
PodExecOptions
:
( pkg / apis / core / types.go )لتنفيذ الإجراءات المطلوبة ، يجب أن يعرف خادم api الخادم الذي يحتاج إلى الاتصال به:
( pkg / registry / core / pod / strategy.go )بالطبع ، يتم أخذ بيانات نقطة النهاية من معلومات المضيف:
nodeName := types.NodeName(pod.Spec.NodeName) if len(nodeName) == 0 {
( pkg / registry / core / pod / strategy.go )الصيحة! يحتوي Kubelet الآن على منفذ (
node.Status.DaemonEndpoints.KubeletEndpoint.Port
) يمكن لخادم واجهة برمجة التطبيقات الاتصال به:
( pkg / kubelet / client / kubelet_client.go )من وثائق اتصال Master-Node> Master to Cluster> apiserver إلى kubelet :
يتم إغلاق هذه الاتصالات على نقطة نهاية HTTPS في kubelet. بشكل افتراضي ، لا يتحقق apiserver من شهادة kubelet ، مما يجعل الاتصال عرضة لـ "الهجمات الوسيطة" (MITMs) وغير آمن للعمل على الشبكات غير الموثوق بها و / أو العامة.
الآن يعرف خادم API نقطة النهاية ويقوم بتأسيس اتصال:
( pkg / registry / core / pod / rest / subresources.go )دعونا نرى ما يحدث على العقدة الرئيسية.
أولاً نكتشف IP لعقدة العمل. في حالتنا ، هذا هو 192.168.205.11:
// any machine $ kubectl get nodes k8s-node-1 -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME k8s-node-1 Ready <none> 9h v1.15.3 192.168.205.11 <none> Ubuntu 16.04.6 LTS 4.4.0-159-generic docker://17.3.3
ثم قم بتثبيت منفذ kubelet (10250 في حالتنا):
// any machine $ kubectl get nodes k8s-node-1 -o jsonpath='{.status.daemonEndpoints.kubeletEndpoint}' map[Port:10250]
الآن حان الوقت للتحقق من الشبكة. هل هناك اتصال مع عقدة العمل (192.168.205.11)؟ إنه هناك! إذا قتلت عملية
exec
، فستختفي ، لذلك أعرف أن الاتصال تم إنشاؤه بواسطة خادم api نتيجة للأمر exec الذي تم تنفيذه.
// master node $ netstat -atn |grep 192.168.205.11 tcp 0 0 192.168.205.10:37870 192.168.205.11:10250 ESTABLISHED …

لا يزال الاتصال بين kubectl وخادم api مفتوحًا. بالإضافة إلى ذلك ، هناك اتصال آخر يربط api-server و kubelet.
3. النشاط على عقدة العمل
الآن دعنا نتصل بالعقدة المنفذة ونرى ما سيحدث عليها.
بادئ ذي بدء ، نرى أنه تم تأسيس الاتصال معها أيضًا (السطر الثاني) ؛ 192.168.205.10 هو عنوان IP للعقدة الرئيسية:
// worker node $ netstat -atn |grep 10250 tcp6 0 0 :::10250 :::* LISTEN tcp6 0 0 192.168.205.11:10250 192.168.205.10:37870 ESTABLISHED
ماذا عن فريق
sleep
لدينا؟ الصيحة ، هي حاضرة أيضًا!
// worker node $ ps -afx ... 31463 ? Sl 0:00 \_ docker-containerd-shim 7d974065bbb3107074ce31c51f5ef40aea8dcd535ae11a7b8f2dd180b8ed583a /var/run/docker/libcontainerd/7d974065bbb3107074ce31c51 31478 pts/0 Ss 0:00 \_ sh 31485 pts/0 S+ 0:00 \_ sleep 5000 …
ولكن الانتظار: كيف كرنك هذا كرنك؟ يوجد برنامج خفي في kubelet يسمح بالوصول إلى واجهة برمجة التطبيقات من خلال المنفذ لطلبات خادم api:
( pkg / kubelet / server / streaming / server.go )يقوم Kubelet بحساب نقطة نهاية الاستجابة لطلبات exec:
func (s *server) GetExec(req *runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) { if err := validateExecRequest(req); err != nil { return nil, err } token, err := s.cache.Insert(req) if err != nil { return nil, err } return &runtimeapi.ExecResponse{ Url: s.buildURL("exec", token), }, nil }
( pkg / kubelet / server / streaming / server.go )لا تخلط. لا يُرجع نتيجة الأمر ، ولكن نقطة النهاية للاتصال:
type ExecResponse struct {
( cri-api / pkg / apis / runtime / v1alpha2 / api.pb.go )تطبق
RuntimeServiceClient
واجهة
RuntimeServiceClient
، التي تعد جزءًا من واجهة وقت تشغيل الحاوية
(لقد كتبنا المزيد عنها ، على سبيل المثال ، هنا - الترجمة تقريبًا) :
قائمة طويلة من cri-api إلى kubernetes / kubernetes يستخدم فقط gRPC لاستدعاء طريقة من خلال واجهة وقت تشغيل الحاوية:
type runtimeServiceClient struct { cc *grpc.ClientConn }
( cri-api / pkg / apis / runtime / v1alpha2 / api.pb.go ) func (c *runtimeServiceClient) Exec(ctx context.Context, in *ExecRequest, opts ...grpc.CallOption) (*ExecResponse, error) { out := new(ExecResponse) err := c.cc.Invoke(ctx, "/runtime.v1alpha2.RuntimeService/Exec", in, out, opts...) if err != nil { return nil, err } return out, nil }
( cri-api / pkg / apis / runtime / v1alpha2 / api.pb.go )حاوية وقت التشغيل مسؤولة عن تطبيق
RuntimeServiceServer
:
قائمة طويلة من cri-api إلى kubernetes / kubernetes 
إذا كان الأمر كذلك ، يجب أن نرى اتصال بين kubelet ووقت تشغيل الحاوية ، أليس كذلك؟ دعونا التحقق من ذلك.
قم بتشغيل هذا الأمر قبل الأمر exec وبعده وابحث عن الاختلافات. في حالتي ، الفرق هو هذا:
// worker node $ ss -a -p |grep kubelet ... u_str ESTAB 0 0 * 157937 * 157387 users:(("kubelet",pid=5714,fd=33)) ...
هممم ... اتصال جديد عبر مآخذ يونيكس بين kubelet (معرف المنتج = 5714) وشيء غير معروف. ماذا يمكن أن يكون؟ هذا صحيح ، هذا هو عامل الميناء (معرف المنتج = 1186)!
// worker node $ ss -a -p |grep 157387 ... u_str ESTAB 0 0 * 157937 * 157387 users:(("kubelet",pid=5714,fd=33)) u_str ESTAB 0 0 /var/run/docker.sock 157387 * 157937 users:(("dockerd",pid=1186,fd=14)) ...
كما تتذكر ، هذه عملية خفيّة لرسو السفن (pid = 1186) تنفذ أمرنا:
// worker node $ ps -afx ... 1186 ? Ssl 0:55 /usr/bin/dockerd -H fd:// 17784 ? Sl 0:00 \_ docker-containerd-shim 53a0a08547b2f95986402d7f3b3e78702516244df049ba6c5aa012e81264aa3c /var/run/docker/libcontainerd/53a0a08547b2f95986402d7f3 17801 pts/2 Ss 0:00 \_ sh 17827 pts/2 S+ 0:00 \_ sleep 5000 ...
4. النشاط في وقت تشغيل الحاوية
دعنا نتفحص الكود المصدري لـ CRI-O لفهم ما يحدث. في Docker ، المنطق مشابه.
يوجد خادم مسؤول عن تطبيق
RuntimeServiceServer
:
( cri-o / server / server.go )
( cri-o / erver / container_exec.go )في نهاية السلسلة ، ينفّذ وقت تشغيل الحاوية أمرًا على عقدة العمل:
( cri-o / internal / oci / runtime_oci.go )
أخيرًا ، تنفذ النواة الأوامر:

تذكير
- يمكن لـ API Server أيضًا بدء اتصال بـ kubelet.
- تتم المحافظة على الاتصالات التالية حتى نهاية جلسة exec التفاعلية:
- بين kubectl والخادم api ؛
- بين api-server و kubectl ؛
- بين kubelet ووقت تشغيل الحاوية.
- يتعذر على Kubectl أو api-server تشغيل أي شيء على عقد الإنتاج. يمكن أن تبدأ Kubelet ، لكن بالنسبة لهذه الإجراءات ، فإنها تتفاعل أيضًا مع وقت تشغيل الحاوية.
موارد
PS من المترجم
اقرأ أيضًا في مدونتنا: