ویرگول
ورودثبت نام
فرهاد صادقی
فرهاد صادقیمهندس نرم افزار، طراحی و راه اندازی سیستم های نرم افزاری بر پایه معماری میکروسرویس
فرهاد صادقی
فرهاد صادقی
خواندن ۱۲ دقیقه·۱۸ روز پیش

آموزش Kubernetes - قسمت نهم (Service و Update Strategy)

Service

فرض کنید ما یک سری پاد از برنامه Payment داریم که روی نودهای مختلف کلاستر بالا آمده اند.

همچنین یک سری پاد هم از برنامه Store روی چند نود دیگر کلاستر بالا آمده اند.

چطور ما داخل کلاستر کوبرنتیز بتوانیم از برنامه Payment به Store وصل شویم؟ چطور از خارج کلاستر کوبرنتیز به برنامه Store وصل شویم؟ باید توجه داشت که ما نمی توانیم به یک پاد مشخص وصل شویم چون یک پاد ممکن است از بین برود، متوقف شود، نمونه های دیگری از آن در نودهای دیگر ایجاد شود. بنابراین چه باید کرد؟!

راهکار تعریف "سرویس" می باشد.

انواع سرویس:

1.ClusterIP: مخصوص ارتباطات داخل کلاستر یا ارتباطات شرقی-غربی (East-West) است.

2.NodePort: برای ارتباطات از خارج کلاستر به داخل آن یعنی ارتباطات شمالی-جنوبی (North-South) می باشد.

3.LoadBalancer: برای ارتباطات از خارج کلاستر به داخل آن یعنی ارتباطات شمالی-جنوبی (North-South) می باشد.

[root@MASTER-01 ~]# kubectl api-resources | grep services services svc v1 true Service

1. ClusterIP

از آنجایی که همه پادهای store در یک deployment قرار دارند بنابراین همه آنها label مشخصی دارند. ما یک آی پی مجازی (VIP) تعریف می کنیم و همه پادهایی که دارای label مشخص هستند را پشت آن VIP قرار می دهیم. هر پادی که خواست به برنامه store وصل شود با آن VIP ارتباط می گیرد.

حتی CoreDNS می تواند به VIP یک نام assign کند و پادهای دیگر بجای VIP به آن نام وصل شوند.

توجه داشته باشید که ما فقط برای برنامه ای که قرار است دیگران به آن وصل شوند "سرویس" تعریف می کنیم و برای سایر برنامه ها نیاز به تعریف سرویس نیست.

برنامه نویس Payment برای وصل شدن به برنامه Store باید نام سرویس Store یا VIP آن را بداند. در واقع باید رکورد DNS سرویس Store را بداند. به این نام رکورد، ClusterIP گفته می شود.

برای ارتباطات Async که ما یک Message Broker بین برنامه A و B قرار می دهیم و می خواهیم A به B وصل شود، نیاز نیست برای B سرویس تعریف کنیم بلکه برای Message Broker سرویس تعریف می کنیم. A به سرویس Message Broker وصل می شود و پیغام خود را می گذارد. همه پادهای B هم به سرویس Message Broker وصل هستند و به محض قرار گرفتن پیام توسط A روی Message Broker، یکی از برنامه های B پیام را برمی دارد.

deployment.yaml

apiVersion: apps/v1 kind: Deployment metadata: name: webapp labels: nginx: 1.16.1 spec: replicas: 1 selector: matchLabels: security: S1 template: metadata: labels: security: S1 spec: - name: nginx image: docker.arvancloud.ir/nginx:1.16.1 imagePullPolicy: IfNotPresent

داخل یک فایل yaml می توان چند manifest نوشت؛ فقط باید آنها را با سه خط تیره (---) از هم جدا کرد.

deployment.yaml

apiVersion: apps/v1 kind: Deployment metadata: name: webapp labels: nginx: 1.16.1 spec: replicas: 1 selector: matchLabels: security: S1 template: metadata: labels: security: S1 spec: - name: nginx image: docker.arvancloud.ir/nginx:1.16.1 imagePullPolicy: IfNotPresent --- apiVersion: v1 kind: Service metadata: name: nginx-web labels: app: nginx spec: selector: security: S1 type: ClusterIP ports: - name: http protocol: TCP port: 80 targetPort: 80

Port: پورت سرویس

TargetPort: پورتی که پاد مقصد روی آن listen می کند

[root@MASTER-01 ~]# kubectl apply -f deployment.yaml deployment.apps/webapp unchanged service/nginx-web created [root@MASTER-01 ~]# kubectl get deploy NAME READY UP-TO-DATE AVAILABLE AGE webapp 1/1 1 1 88s [root@MASTER-01 ~]# kubectl get rs NAME DESIRED CURRENT READY AGE webapp-68cb45785d 1 1 1 94s [root@MASTER-01 ~]# kubectl get po NAME READY STATUS RESTARTS AGE webapp-68cb45785d-mggjl 1/1 Running 0 102s [root@MASTER-01 ~]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-web ClusterIP 10.98.247.120 <none> 80/TCP 37s

حالا می توانیم برنامه مورد نظر را از پشت سرویس فراخوانی کنیم:

[root@WORKER-01 ~]# curl http://10.98.247.120 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>

برای اینکه بفهمیم پشت یک سرویس چه IPهایی هست:

[root@MASTER-01 ~]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 62d nginx-web ClusterIP 10.98.247.120 <none> 80/TCP 7m9s [root@MASTER-01 ~]# kubectl get ep nginx-web Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice NAME ENDPOINTS AGE nginx-web 10.100.171.40:80 7m32s

روش رفتن داخل یک پاد و اجرای دستور داخل آن:

$ kubectl exec -it podname --bash Or $ kubectl exec -it podname --sh $ kubectl exec -it nginx-master-01 --bash

اگر پاد A و پاد B در namespace های جداگانه ای باشند آنگاه نام namespace در نحوه resolve کردن تاثیر دارد.


2. NodePort

برای ارتباط با یک پاد (سرویس) از خارج کلاستر کوبرنتیز ما نیاز به تعریف NodePort روی نودها داریم. در این حالت کاربر می تواند به nodeport هر یک از نودها متصل شود. درخواست کاربر از nodeport به ClusterIP سرویس NAT می شود. فرقی نمی کند کاربر به کدام یک از نودها متصل شود.

اگر ما پورت برای NodePort مشخص نکنیم خود کوبرنتیز یک پورت رندوم بین 30000 تا 32767 به آن تخصیص می دهد.

deployment2.yaml

apiVersion: apps/v1 kind: Deployment metadata: name: webapp labels: nginx: 1.16.1 spec: replicas: 1 selector: matchLabels: security: S1 template: metadata: labels: security: S1 spec: - name: nginx image: docker.arvancloud.ir/nginx:1.16.1 imagePullPolicy: IfNotPresent --- apiVersion: v1 kind: Service metadata: name: nginx-web labels: app: nginx spec: selector: security: S1 type: NodePort ports: - name: http protocol: TCP port: 80 targetPort: 80 nodePort: 30000
[root@MASTER-01 ~]# kubectl apply -f deployment2.yaml deployment.apps/webapp unchanged service/nginx-web configured [root@MASTER-01 ~]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 62d nginx-web NodePort 10.98.247.120 <none> 80:30000/TCP 46m

حالا اگر در مرورگر خود آی پی هر یک از نودها را به همراه پورت 30000 وارد کنیم صفحه nginx را مشاهده می کنیم.

http://192.168.65.45:30000

http://192.168.65.46:30000

http://192.168.65.47:30000

[root@WORKER-02 ~]# curl http://192.168.65.46:30000 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>

از آنجایی که در NodePort ترافیک NAT می شود، بنابراین ما در برنامه خود IP کلاینت را نداریم و همه درخواستها با یک IP به دست برنامه می رسد. برای اینکه آی پی کاربر را داشته باشیم، باید در تجهیزات و سرویسهای بین مسیر X-Forwarded-For تنظیم شود. اینکار باعث می شود که زنجیره آی پی ها در هدر x-forwarded-for درخواست http قرار بگیرد.

فرض کنید ما بخواهیم این محدودیت را روی سرویس بگذاریم که اگر ترافیک به دست یک نود رسید در صورتی که پاد مقصد روی آن نود باشد ادامه مسیر بدهد و اگر پاد روی آن نود نباشد مانع شود. (در شکل بالا اگر ترافیک به نود سمت راست برسد نباید امکان فراخوانی داده شود)

apiVersion: v1 kind: Service metadata: name: nginx-web labels: app: nginx spec: selector: security: S1 type: NodePort ports: - name: http protocol: TCP port: 80 targetPort: 80 nodePort: 30000 externalTrafficPolicy: Local

3. LoadBalancer

در Cloud Provider ها ممکن است یک Load Balancer بیرون از کلاستر کوبرنتیز در اختیار ما گذاشته شود.

F5 BigIP

F5 BigIP سخت افزاری است که بیرون از کلاستر کوبرنتیز می باشد و با کوبرنتیز integrate می شود. درخواستها ابتدا به F5 می رسد و F5 درخواست را به سرویس مربوطه در کلاستر کوبرنتیز ارجاع می دهد. توجه داشته باشید که این تجهیز سخت افزاری مشابه یک پاد داخل کلاستر عمل می کند.

metallb

کوبرنتیز از iptable و kubeproxy برای انجام عمل پروکسی استفاده می کند. اما این ابزارها در لود بالا احتمالا کند عمل خواهند کرد. بنابراین تصمیم گرفتند که پروتکلی را درست کنند که با استفاده از خود کرنل لینوکس عملیات پروکسی کردن را انجام دهد و به این ترتیب پروتکل eBPF ایجاد شد.

CNI (Network Driver) --> Calico , Cilium , ...

Cilium پروتکل eBPF را در دل خود پیاده سازی کرده است و اگر ما از Cilium بجای Calico استفاده کنیم، می تواند مستقلا عمل لود بالانس را انجام دهد و نیاز به ابزار دیگری مانند metallb نداشتیم.

نصب metallb

  • imageهای controller و speaker را در containerd همه نودهای کلاستر ایمپورت می کنیم.

  • apply کردن فایلهای metallb.yaml

[root@MASTER-01 ~]# kubectl apply -f metallb.yaml [root@MASTER-01 ~]# kubectl get po -n metallb-system -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES controller-66bdd896c6-92vt9 1/1 Running 1 (6d11h ago) 6d11h 10.103.204.68 worker-02 <none> <none> speaker-92vrg 1/1 Running 0 6d11h 192.168.65.14 worker-01 <none> <none> speaker-bzhcs 1/1 Running 0 6d11h 192.168.65.15 worker-02 <none> <none> speaker-gkmcb 1/1 Running 0 6d11h 192.168.65.13 master-01 <none> <none>

بعد از apply کردن metallb.yaml یک پاد controller و به تعداد نودها پاد speaker بالا می آید.

  • قاعدتا ما یک یا چند IP ثابت در اختیار داریم که از خارج از کلاستر کوبرنتیز در دسترس باشند. در فایل metallb-l2.yaml ما لیست اینIPها را به عنوان IP Pool وارد کرده و آن را apply می کنیم. هر کس که از metallb درخواست سرویس لودبالانسر کند، metallb یکی از IPها را برداشته و به عنوان IP ورودی خود استفاده می کند.

[root@MASTER-01 ~]# kubectl apply -f metallb-l2.yaml
  • در انتها نیاز است تا فایل metallb-advertise.yaml را apply کنیم. این کار به سیستم اعلام می کند که IPهای موجود در IP Pool در اختیار metallb می باشد و ارسال ترافیک از این IPها به metallb بلامانع است.

[root@MASTER-01 ~]# kubectl apply -f metallb-advertise.yaml

تا اینجای کار metallb نصب شد. حالا باید یک سرویس از نوع LoadBalancer بنویسیم که ترافیک را روی یکی از IPهای موجود در IP pool دریافت کند و آن را به pod مپ کند.

تعریف manifest و ایجاد سرویس به دو صورت می تواند انجام شود:

Declarative یا توصیفی؛ یعنی همان روشی که تا کنون انجام می دادیم و مشخصات سرویس را در قالب فایل yaml تعریف می کردیم.

Imperative یا قدم به قدم؛ یعنی با اجرای یک یا چند دستور اقدام به ایجاد سرویس نماییم.

دستور زیر ایجاد سرویس بصورت Imperative را نشان می دهد. خروجی آن در فایل nginx-web.yaml ذخیره می شود.

[root@MASTER-01 ~]# kubectl create deployment nginx-web --image=docker.arvancloud.ir/nginx:1.17.1 --replicas=2 --dry-run=client -o yaml > nginx-web.yaml

dry-run=client یعنی اینکه این کامند را واقعا اجرا نکن فقط خروجی اجرا را نشان بده.

[root@MASTER-01 session-09]# vim nginx-web.yaml apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx-web name: nginx-web spec: replicas: 2 selector: matchLabels: app: nginx-web strategy: {} template: metadata: labels: app: nginx-web spec: containers: - image: docker.arvancloud.ir/nginx:1.17.1 name: nginx imagePullPolicy: IfNotPresent

اضافات فایل nginx-web.yaml را پاک کرده و آن را apply می کنیم.

[root@MASTER-01 ~]# kubectl apply -f nginx-web.yaml deployment.apps/nginx-web created [root@MASTER-01 ~]# kubectl get po NAME READY STATUS RESTARTS AGE nginx-web-8558c77df5-8s2s2 1/1 Running 0 29s nginx-web-8558c77df5-8tnln 1/1 Running 0 29s

خروجی دستور زیر که برای ایجاد سرویس از نوع LoadBalancer می باشد، به انتهای فایل nginx-web.yaml اضافه می شود.

[root@MASTER-01 ~]# kubectl expose deployment nginx-web --name nginx-svc --type=LoadBalancer --port=80 --dry-run=client -o yaml >> nginx-web.yaml

[root@MASTER-01 ~]# vim nginx-web.yaml apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx-web name: nginx-web spec: replicas: 2 selector: matchLabels: app: nginx-web strategy: {} template: metadata: labels: app: nginx-web spec: containers: - image: docker.arvancloud.ir/nginx:1.17.1 name: nginx imagePullPolicy: IfNotPresent --- apiVersion: v1 kind: Service metadata: labels: app: nginx-web name: nginx-svc spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: app: nginx-web type: LoadBalancer

[root@MASTER-01 session-09]# kubectl apply -f nginx-web.yaml deployment.apps/nginx-web unchanged service/nginx-svc created [root@MASTER-01 session-09]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 63d nginx-svc LoadBalancer 10.111.82.92 <pending> 80:30179/TCP 3m23s

به این ترتیب LoadBalancer روی External-IP بالا می آید و حالا می توانیم از خارج از کلاستر کوبرنتیز به این IP درخواست بزنیم و LoadBalancer درخواست ما را به یکی از پادها ارسال می کند.

تفاوت NodePort و LoadBalancer در این است که در LoadBalancer کلاینت با یک IP مشخص طرف است و این IP ثابت است. این IP یک IP شناور (Floating IP) می باشد و حتی اگر LoadBalancer متوقف شود و روی یک نود دیگر مجددا بالا بیاید، آن IP به نود جدید اختصاص می یابد. اما در NodePort سرویس از طریق IP هر یک از نودها قابل دسترس هست.


Update Strategies

در کوبرنتیز برای بروزرسانی نرم افزارها از یک نسخه به نسخه دیگر، استراتژی های مختلفی وجود دارد. پرکاربردترین آنها Rolling Update و ReCreate می باشند. سایر استراتژی ها مانند Blue-Green و Canary کاربرد خاص منظوره و کمتری دارند.

Rolling Update:

در این روش، تعدادی از پادهای نسخه فعلی حذف می شوند و تعدادی پاد نسخه جدید ایجاد می گردند. اگر پادهای جدید با موفقیت اجرا شوند، مجددا تعداد دیگری از پادهای نسخه فعلی حذف و تعدادی پاد نسخه جدید اجرا می شود. این روند تا زمانی که همه پادهای نسخه فعلی حذف و همه پادهای نسخه جدید ایجاد شوند، ادامه می یابد.

در این روش، تقریبا Downtime صفر داریم و در طول مدت زمان آپدیت، سرویس بالاست.

ReCreate:

در این روش همه پادهای نسخه فعلی حذف می شوند و سپس به همان تعداد پاد نسخه جدید ایجاد می گردد.

اگر تغییرات انجام شده در نسخه جدید برنامه، ساختار جداول دیتابیس را تغییر دهد و امکان اجرا و ثبت اطلاعات توسط نسخه قبلی برنامه وجود نداشته باشد، ما مجبور هستیم از استراتژی Recreate استفاده کنیم. چون نسخه های قدیمی امکان کار با دیتابیس جدید را نخواهند داشت و دچار مشکل می شوند.

apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx-web name: nginx-web spec: replicas: 6 selector: matchLabels: app: nginx-web strategy: rollingUpdate: maxSurge: 1 maxUnavailable: 1 template: metadata: labels: app: nginx-web spec: containers: - image: docker.arvancloud.ir/nginx:1.17.1 name: nginx imagePullPolicy: IfNotPresent

برای بروزرسانی نسخه ایمیج از دستور زیر هم می توانیم استفاده کنیم:

[root@MASTER-01 ~]# kubectl set image nginx-web nginx=docker.arvancloud.ir/nginx:1.17.1

مشاهده تاریخچه آپدیت های Deployment با نام redis-dep:

[root@MASTER-01 ~]# kubectl rollout history deployment redis-dep deployment.apps/redis-dep REVISION CHANGE-CAUSE 1 <none> 2 <none> 3 <none>

مشاهده جزئیات revision اول:

[root@MASTER-01 ~]# kubectl rollout history deployment redis-dep --revision=1 deployment.apps/redis-dep with revision #1 Pod Template: Labels: app=redis-app pod-template-hash=67dc46768b Containers: redis: Image: apk-reg.aradarpanet.ir/class/redis:latest Port: <none> Host Port: <none> Environment: <none> Mounts: <none> Volumes: <none> Node-Selectors: <none> Tolerations: <none>

سایر دستورات و کاربردهای rollout را در زیر مشاهده می کنید:

[root@MASTER-01 ~]# kubectl rollout --help Manage the rollout of one or many resources. Valid resource types include: * deployments * daemonsets * statefulsets Examples: # Rollback to the previous deployment kubectl rollout undo deployment/abc # Check the rollout status of a daemonset kubectl rollout status daemonset/foo # Restart a deployment kubectl rollout restart deployment/abc # Restart deployments with the 'app=nginx' label kubectl rollout restart deployment --selector=app=nginx Available Commands: history View rollout history pause Mark the provided resource as paused restart Restart a resource resume Resume a paused resource status Show the status of the rollout undo Undo a previous rollout Usage: kubectl rollout SUBCOMMAND [options] Use "kubectl rollout <command> --help" for more information about a given command. Use "kubectl options" for a list of global command-line options (applies to all commands).

کوبرنتیز برای هر update و rollback یک Replicaset جدید می سازد و با تغییر عدد DESIRED در Replicasetها، عملیات آپدیت و rollback را انجام می دهد. به همین خاطر اگر لیست Replicasetها را مشاهده کنیم، خواهیم دید که تعدادی Replicaset با عدد DESIRED صفر وجود دارد.

kubernetes
۱
۰
فرهاد صادقی
فرهاد صادقی
مهندس نرم افزار، طراحی و راه اندازی سیستم های نرم افزاری بر پایه معماری میکروسرویس
شاید از این پست‌ها خوشتان بیاید