الحد الأدنى من Kubernetes القابلة للحياة

تم إعداد ترجمة المقال عشية بدء الدورة "ممارسات وأدوات DevOps".

الحد الأدنى من Kubernetes القابلة للحياة

إذا كنت تقرأ هذا، فمن المحتمل أنك سمعت شيئًا عن Kubernetes (وإذا لم يكن الأمر كذلك، فكيف انتهى بك الأمر هنا؟) ولكن ما هو Kubernetes بالضبط؟ هذا "تنسيق الحاويات الصناعية"؟ أو "نظام التشغيل السحابي الأصلي"؟ ماذا يعني هذا حتى؟

لأكون صادقًا، لست متأكدًا بنسبة 100%. لكنني أعتقد أنه من المثير للاهتمام التعمق في التفاصيل الداخلية ومعرفة ما يحدث بالفعل في Kubernetes تحت طبقاته العديدة من التجريدات. لذا، من أجل المتعة فقط، دعونا نلقي نظرة على الشكل الفعلي لـ "مجموعة Kubernetes" البسيطة. (سيكون هذا أسهل بكثير من Kubernetes الطريق الصعب.)

أفترض أن لديك معرفة أساسية بـ Kubernetes وLinux والحاويات. كل ما نتحدث عنه هنا هو لأغراض البحث/التعلم فقط، فلا تضع أيًا منه قيد الإنتاج!

مراجعة

يحتوي Kubernetes على العديد من المكونات. وفق ويكيبيديا، تبدو الهندسة المعمارية كما يلي:

الحد الأدنى من Kubernetes القابلة للحياة

هناك ثمانية مكونات على الأقل موضحة هنا، لكننا سنتجاهل معظمها. أريد أن أشير إلى أن الحد الأدنى الذي يمكن أن يسمى Kubernetes بشكل معقول يتكون من ثلاثة مكونات رئيسية:

  • مكعبة
  • kube-apserver (الذي يعتمد على etcd - قاعدة بياناته)
  • وقت تشغيل الحاوية (Docker في هذه الحالة)

دعونا نرى ما تقوله الوثائق عن كل واحد منهم (الروسية., الإنجليزية.). في البدايه مكعبة:

وكيل يعمل على كل عقدة في المجموعة. يتأكد من تشغيل الحاويات في الكبسولة.

يبدو بسيطا بما فيه الكفاية. ماذا عن أوقات تشغيل الحاوية (وقت تشغيل الحاوية)؟

وقت تشغيل الحاوية هو برنامج مصمم لتشغيل الحاويات.

مليء بالمعلومات. ولكن إذا كنت على دراية بـ Docker، فيجب أن تكون لديك فكرة عامة عما يفعله. (تفاصيل فصل المسؤوليات بين وقت تشغيل الحاوية وkubelet هي في الواقع دقيقة جدًا ولن أخوض فيها هنا.)

И خادم API?

خادم API هو مكون لوحة تحكم Kubernetes الذي يعرض واجهة برمجة تطبيقات Kubernetes. خادم API هو جانب العميل في لوحة تحكم Kubernetes

كان على أي شخص قام بأي شيء باستخدام Kubernetes أن يتفاعل مع واجهة برمجة التطبيقات (API) إما بشكل مباشر أو من خلال kubectl. هذا هو جوهر ما يجعل Kubernetes Kubernetes - العقل الذي يحول جبال YAML التي نعرفها ونحبها جميعًا (؟) إلى بنية تحتية عاملة. يبدو من الواضح أن واجهة برمجة التطبيقات (API) يجب أن تكون موجودة في الحد الأدنى من التكوين لدينا.

الشروط المسبقة

  • جهاز Linux افتراضي أو فعلي مع إمكانية الوصول إلى الجذر (أنا أستخدم Ubuntu 18.04 على جهاز افتراضي).
  • وهذا كل شيء!

تركيب ممل

نحتاج إلى تثبيت Docker على الجهاز الذي سنستخدمه. (لن أخوض في التفاصيل حول كيفية عمل Docker والحاويات؛ إذا كنت مهتمًا، فهناك مقالات رائعة). دعونا فقط تثبيته مع apt:

$ sudo apt install docker.io
$ sudo systemctl start docker

بعد ذلك، نحتاج إلى الحصول على ثنائيات Kubernetes. في الواقع، نحتاج فقط للإطلاق الأولي لـ "مجموعتنا". kubelet، حيث يمكننا استخدامها لتشغيل مكونات الخادم الأخرى kubelet. للتفاعل مع مجموعتنا بعد تشغيلها، سنستخدم أيضًا kubectl.

$ curl -L https://dl.k8s.io/v1.18.5/kubernetes-server-linux-amd64.tar.gz > server.tar.gz
$ tar xzvf server.tar.gz
$ cp kubernetes/server/bin/kubelet .
$ cp kubernetes/server/bin/kubectl .
$ ./kubelet --version
Kubernetes v1.18.5

ماذا يحدث إذا ركضنا للتو kubelet?

$ ./kubelet
F0609 04:03:29.105194    4583 server.go:254] mkdir /var/lib/kubelet: permission denied

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:

$ mkdir pods
$ sudo ./kubelet --pod-manifest-path=pods

بعد ذلك، في نافذة طرفية/tmux أخرى/أيًا كان، سنقوم بإنشاء بيان pod:

$ cat <<EOF > pods/hello.yaml
apiVersion: v1
kind: Pod
metadata:
  name: hello
spec:
  containers:
  - image: busybox
    name: hello
    command: ["echo", "hello world!"]
EOF

kubelet يبدأ بكتابة بعض التحذيرات ويبدو أن شيئًا لم يحدث. ولكن هذا ليس صحيحا! دعونا نلقي نظرة على عامل الميناء:

$ 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 قرأت بيان الكبسولة وأعطيت Docker الأمر بإطلاق حاويتين وفقًا لمواصفاتنا. (إذا كنت تتساءل عن حاوية "الإيقاف المؤقت"، فهي عبارة عن اختراق لـ Kubernetes - راجع هذه المدونة.) ستطلق Kubelet حاويتنا busybox باستخدام الأمر المحدد وسيتم إعادة تشغيله إلى أجل غير مسمى حتى يتم حذف الكبسولة الثابتة.

أهنئ نفسك. لقد توصلنا للتو إلى إحدى الطرق الأكثر إرباكًا لإخراج النص إلى الجهاز!

إطلاق الخ

هدفنا النهائي هو تشغيل Kubernetes API، ولكن للقيام بذلك نحتاج أولاً إلى التشغيل إلخ. لنبدأ مجموعة etcd البسيطة عن طريق وضع إعداداتها في دليل pods (على سبيل المثال، pods/etcd.yaml):

apiVersion: v1
kind: Pod
metadata:
  name: etcd
  namespace: kube-system
spec:
  containers:
  - name: etcd
    command:
    - etcd
    - --data-dir=/var/lib/etcd
    image: k8s.gcr.io/etcd:3.4.3-0
    volumeMounts:
    - mountPath: /var/lib/etcd
      name: etcd-data
  hostNetwork: true
  volumes:
  - hostPath:
      path: /var/lib/etcd
      type: DirectoryOrCreate
    name: etcd-data

إذا سبق لك العمل مع Kubernetes، فمن المفترض أن تكون ملفات YAML هذه مألوفة لك. هناك نقطتان فقط جديرتان بالملاحظة هنا:

لقد قمنا بتثبيت المجلد المضيف /var/lib/etcd في الكبسولة بحيث يتم الاحتفاظ بالبيانات وما إلى ذلك بعد إعادة التشغيل (إذا لم يتم ذلك، فسيتم مسح حالة المجموعة في كل مرة يتم فيها إعادة تشغيل الكبسولة، وهو ما لن يكون جيدًا حتى مع الحد الأدنى من تثبيت Kubernetes).

لقد قمنا بتثبيت hostNetwork: true. من غير المستغرب أن يقوم هذا الإعداد بتكوين etcd لاستخدام الشبكة المضيفة بدلاً من الشبكة الداخلية للقرص (وهذا سيسهل على خادم 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

يعد تشغيل خادم Kubernetes API أسهل. المعلمة الوحيدة التي يجب تمريرها هي --etcd-servers، يفعل ما تتوقعه:

apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - name: kube-apiserver
    command:
    - kube-apiserver
    - --etcd-servers=http://127.0.0.1:2379
    image: k8s.gcr.io/kube-apiserver:v1.18.5
  hostNetwork: true

ضع ملف YAML هذا في الدليل podsوسيبدأ خادم API. التحقق مع curl يُظهر أن واجهة برمجة تطبيقات Kubernetes تستمع على المنفذ 8080 مع وصول مفتوح تمامًا - لا يلزم المصادقة!

$ curl localhost:8080/healthz
ok
$ curl localhost:8080/api/v1/pods
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "selfLink": "/api/v1/pods",
    "resourceVersion": "59"
  },
  "items": []
}

(مرة أخرى، لا تقم بتشغيل هذا في الإنتاج! لقد فوجئت قليلاً بأن الإعداد الافتراضي غير آمن للغاية. لكنني أعتقد أن هذا يجعل التطوير والاختبار أسهل.)

والمفاجأة السارة أن 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 بمجموعة بسيطة للغاية من معلمات سطر الأوامر، لذلك لا يعرف kubelet كيفية الاتصال بخادم API وإخطاره بحالته. وبعد دراسة التوثيق نجد العلم المقابل:

--kubeconfig string

المسار إلى الملف kubeconfigالذي يحدد كيفية الاتصال بخادم API. التوفر --kubeconfig تمكين وضع خادم API، لا --kubeconfig تمكين وضع غير متصل بالشبكة.

طوال هذا الوقت، دون أن نعرف ذلك، كنا نقوم بتشغيل kubelet في "وضع عدم الاتصال". (إذا كنا متحذلقين، فيمكننا أن نفكر في نظام kubelet المستقل باعتباره "الحد الأدنى من Kubernetes القابل للحياة"، ولكن هذا سيكون مملًا للغاية). لكي يعمل التكوين "الحقيقي"، نحتاج إلى تمرير ملف kubeconfig إلى kubelet حتى يعرف كيفية التحدث إلى خادم API. لحسن الحظ، الأمر بسيط للغاية (نظرًا لعدم وجود أي مشكلات تتعلق بالمصادقة أو الشهادات):

apiVersion: v1
kind: Config
clusters:
- cluster:
    server: http://127.0.0.1:8080
  name: mink8s
contexts:
- context:
    cluster: mink8s
  name: mink8s
current-context: mink8s

احفظ هذا باسم kubeconfig.yaml، قتل العملية kubelet وأعد التشغيل باستخدام المعلمات الضرورية:

$ sudo ./kubelet --pod-manifest-path=pods --kubeconfig=kubeconfig.yaml

(بالمناسبة، إذا حاولت الوصول إلى واجهة برمجة التطبيقات عبر حليقة عندما لا يكون kubelet قيد التشغيل، فستجد أنه لا يزال قيد التشغيل! Kubelet ليس "أصلًا" لقرونه مثل Docker، فهو أشبه بـ "عنصر تحكم" daemon." ستستمر الحاويات التي يديرها kubelet في العمل حتى يوقفها kubelet.)

في بضع دقائق 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) على فعله. لنبدأ مع جراب nginx:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx

وهنا نحصل على خطأ مثير للاهتمام إلى حد ما:

$ ./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 "غبية" بشكل مدهش - عندما تقوم بإنشاء Pod في واجهة برمجة التطبيقات، فإنها تسجلها، لكنها لا تحاول معرفة العقدة التي سيتم تشغيلها عليها.

في الواقع، لا تحتاج إلى برنامج جدولة لتشغيل الكبسولة. يمكنك إضافة عقدة يدويًا إلى البيان في المعلمة nodeName:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
  nodeName: mink8s

(يستبدل 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>

للتأكد من أن الشبكة بين البودات تعمل بشكل صحيح، يمكننا تشغيل عملية التجعيد من كبسولة أخرى:

$ 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 يعملان كما هو متوقع، لكن الخدمة والنشر لا يعملان.

النجاح!

لقد أصبح هذا المنشور طويلاً، لذا سأعلن النصر وأقول إن هذا التكوين قابل للتطبيق ويمكن تسميته "Kubernetes". للتلخيص: أربعة ثنائيات، وخمسة معلمات سطر أوامر و45 سطرًا "فقط" من YAML (وليس هذا كثيرًا وفقًا لمعايير Kubernetes) ولدينا عدد لا بأس به من الأشياء التي تعمل:

  • تتم إدارة البودات باستخدام واجهة برمجة تطبيقات Kubernetes العادية (مع بعض الاختراقات)
  • يمكنك تحميل وإدارة صور الحاويات العامة
  • تظل البودات على قيد الحياة ويتم إعادة تشغيلها تلقائيًا
  • تعمل الشبكات بين القرون داخل نفس العقدة بشكل جيد
  • يعمل ConfigMap وSecret وبسيط على تخزين التخزين كما هو متوقع

لكن الكثير مما يجعل Kubernetes مفيدًا حقًا لا يزال مفقودًا، مثل:

  • قرنة جدولة
  • المصادقة/الترخيص
  • عقد متعددة
  • شبكة الخدمات
  • DNS الداخلي متفاوت المسافات
  • وحدات التحكم لحسابات الخدمة وعمليات النشر والتكامل مع موفري الخدمات السحابية ومعظم الميزات الأخرى التي يقدمها Kubernetes

إذن ما الذي حصلنا عليه بالفعل؟ تعد واجهة برمجة تطبيقات Kubernetes، التي تعمل بمفردها، مجرد منصة لـ أتمتة الحاويات. إنها لا تفعل الكثير - إنها مهمة للعديد من وحدات التحكم والمشغلين الذين يستخدمون واجهة برمجة التطبيقات - ولكنها توفر بيئة متسقة للأتمتة.

تعرف على المزيد حول الدورة في الندوة المجانية على الويب.

اقرأ أكثر:

المصدر: www.habr.com

إضافة تعليق