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

آموزش Kubernetes - قسمت دهم (Storage)

فرض کنید ما یک فضای ذخیره سازی مانند NFS داریم. حالا ما یک پاد بالا می آوریم و می خواهیم مسیر data/ آن را به NFS وصل کنیم.

NFS (Network File System): پروتکل دسترسی به فایل تحت شبکه

این کار مزایایی برای ما خواهد داشت، از جمله:

اطلاعات پاد persistent می شود و با از بین رفتن پاد، اطلاعات آن همچنان پا برجاست.

از طرف دیگر ما به راحتی می توانیم پاد خود را scale کنیم و تعداد بیشتری از آن را بالا بیاوریم که همگی از یک منبع ذخیره سازی استفاده می کنند و دیتاهای مشترکی دارند.

نصب NFS

~ yum -y install nfs-utils ~ systemctl enable --now nfs-server

mkdir /var/nfs vim /etc/exports

در فایل etc/exports باید مشخص کنیم که چه رنج IPهایی به چه فولدرهایی دسترسی دارند. هر درخواست که از هر پاد صادر می شود به آی پی Node آن پاد مپ می شود، بنابراین در این قسمت IP نودها مدنظر است.

/var/nfs 192.168.65.0/24(rw,sync,no_root_squash,no_all_squash)

systemctl restart nfs-server

برخی از storage solution ها را کوبرنتیز می شناسد مانند NFS و فقط کافی است آنها را نصب کنید اما برخی دیگر را باید به کوبرنتیز بشناسانیم، برای این کار باید CSI driver آنها را نصب کنیم.

storage های سخت افزاری مانند SAN Storage بسیار گران قیمت هستند و خیلی از شرکت های هاستینگ و نرم افزاری سراغ Software Defined Storageها می روند. مانند CEPH که یک ذخیره ساز Distributed می باشد. یعنی می توانیم Replica های مختلف در چند دیتاسنتر داشته باشیم.

Software Defined Storages:

.........................................................................................

CEPH (Object Storage, Block Storage, File Storage)

GlusterFS (File Storage)

Minio (Object Storage)

Open EBS (Block Storage)

LongHorn (Block Storage)

VSAN (Block Storage)

Access Modes

.........................................................................................

ReadwriteOnce

ReadOnlyMany (RX)

ReadWriteMany (RWX)

ReadWriteOncePod

Software Defined Storage ها به سه روش دیسک در اختیار ما قرار می دهند:

Block Storage:

storage را به بلاک های خام تقسیم بندی می کنیم. روی هر یک از بلاک ها باید file system نصب شود و پارتیشن بندی انجام شود تا بتوان از آن استفاده کرد.

از جمله کاربردهای Block storage برای راه اندازی ماشین های مجازی می باشد، یعنی فضای ذخیره سازی را به بلاک هایی تقسیم بندی می کنند، هر فضا را به یک VM تخصیص می دهند و آن VM خودش روی فضای تخصیص داده شده file system نصب کرده و آن را پارتیشن بندی می کند.

File Storage:

نیازی به پارتیشن بندی و نصب فایل سیستم وجود ندارد و بلافاصله بعد از راه اندازی قابل استفاده هستند. NFS نمونه ای از file storage ها است. file storage ها عموما over network هستند.

از جمله کاربردهای File storage ایجاد فضای اشتراک فایل می باشد (فولدرها یا پارتیشن های Share). یعنی دیتا یک جای دیگر و روی سروری مشخص ذخیره می شود و شما با پروتکل های شبکه به آن متصل می شوید.

Object Storage:

در object storage فایل یا محتوایی که می خواهیم ذخیره سازی کنیم بصورت object در باکت ذخیره سازی می شود. در این حالت object همانطور که over http منتقل می شود، همانطور هم ذخیره می شود و موقع بازیابی هم، به همان صورت از باکت خوانده شده و over http منتقل می شود.

در Object storage یک باکت ساخته می شود، access key و secret key و url تحویل برنامه داده می شود. برنامه با استفاده از این کلیدها می تواند در باکت فایل ذخیره کند. در Object storage ما کاری با فرمت و نحوه ذخیره سازی اطلاعات در سرور نداریم. فایل خود را over http به آن می دهیم و over http هم از آن می گیریم.


Assign storage to POD

PV (Persistence Volume):

ابتدا باید یک PV تعریف کنیم. PV یک مفهوم logical است. در هر PV باید مشخص شود که چقدر فضا دارد؟ روی چه سخت افزاری قرار دارد؟ و سطح دسترسی به آن به چه شکلی است؟

PV:

Size: 5 Gi

Storage Solution: NFS

accessMode: read/write/many

kubectl api-resources | grep pv

pv.yaml

apiVersion: v1 kind: PersistentVolume metadata: name: db-pv spec: capacity: storage: 5Gi accessMode: - ReadWriteMany storageClassName: nfs nfs: server: 192.168.65.43 path: /var/nfs

دقت داشته باشید که نیاز نیست ما NFS را روی همه نودها نصب کنیم بلکه آن را روی یک نود نصب می کنیم و بقیه از آن استفاده می کنند، مانند دیتابیس.

kubectl apply -f pv.yaml kubectl get pv

PVC (Persistence Volume Claim):

حالا فرض کنید یک پاد درخواست 10 Gi فضا داشته باشد، به این درخواست claim می گویند.

PVC:

Size: 10 Gi

accessMode: read/write/many

هر درخواست (PVC) باید به یک storage (PV) اصطلاحا Bound شود.

اگر فضای درخواست شده بیشتر از فضای PV باشد، آن درخواست pending می ماند.

kubectl api-resources | grep pvc

pvc.yaml

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: vote-app-pvc spec: accessModes: - ReadWriteMany storageClassName: nfs resources: requests: storage: 5Gi
kubectl apply -f pvc.yaml kubectl get pv,pvc

postgres.yaml

template: metadata: labels: app: postgres spec: containers: - image: apk-reg.aradarpanet.ir/class/postgres:15 name: postgres imagePullPolicy: IfNotPresent volumeMounts: - mountPath: /var/lib/postgresql/data/ name: vote-nfs env: - name: POSTGRES_USER value: postgres - name: POSTGRES_PASSWORD value: postgres volumes: - name: vote-nfs persistentVolumeClaim: claimName: vote-app-pvc

بعد از apply کردن postgres.yaml، احتمالا اجرای پاد با خطا مواجه می شود چون پروتکل nfs روی نودی که پاد روی آن اجرا می شود نصب نشده است و ما باید nfs-client را روی آن نود نصب کنیم تا بتواند با nfs-server ارتباط بگیرد.

yum -y install nfs-client yum -y install nfs-utils

Reclaim Policy:

فرض کنید ما یک PV و PVC تعریف کردیم و این دو به هم bound شدند و PVC به یک پاد تخصیص داده شد. حال اگر آن پاد حذف شود وضعیت PV چه خواهد بود؟! آیا با حذف شدن PVC آن، PV هم آزاد می شود و می توان آن را در اختیار پاد دیگر قرار داد؟! اینها سوالاتی است که توسط Reclaim Policy پاسخ داده می شوند.

Retain:

در Retain وقتی PV ایجاد می شود، وضعیت آن ابتدا Available است. اولین درخواست PVC که وارد شود و منابع خواسته شده، با PV موجود سازگار باشد آن PV به PVC متصل می شود و وضعیت PV به Bound تغییر می کند. حالا اگر PVC از بین برود، وضعیت PV به Available تغییر نمی کند بلکه وضعیت آن Released می شود. یعنی درست است که در حال حاضر PV ما به هیچ PVC ای باند نشده است ولی دیتای PVC قبلی روی آن قرار دارد و نمی توان آن PV را در اختیار PVC جدید قرار داد. در این حالت برای اینکه ما منابع را آزاد کنیم باید ابتدا PV را حذف کنیم. سپس دیتای موجود در storage مربوطه را هم پاک کنیم و در آخر یک PV جدید بسازیم تا PVC های درخواست شده بتوانند به آن bound شوند.

Retain Policy زمانی کاربرد دارد که ما نمی خواهیم اطلاعات کاربر قبلی توسط کاربر بعدی قابل دسترس باشد. معمولا cloud providerهای معروف از این policy استفاده می کنند. فرض کنید شما از خدمات ابری آمازون (AWS) یک سرویس دریافت کرده اید و برای سرویس شما PVC و PV ایجاد شده است حال اگر شما سرویس خود را لغو کنید یا آن را تمدید نکنید، PVC شما حذف می شود اگر آمازون PV شما را هم آزاد کند و آن را به کاربر بعدی تخصیص دهد، آن کاربر ممکن است فایلها و اطلاعت شخصی شما را مشاهده کند. بنابراین آمازون از Retain Policy در این مورد استفاده می کند.

Delete:

در Delete Policy اگر PVC حذف شود، PV و اطلاعات داخل آن هم بصورت خودکار حذف می شوند. انتخاب پالیسی Delete نیازمند این است که storage ما از این قابلیت پشتیبانی کند.


Container Storage Interface (CSI)

برای نصب CSI driver مربوط به NFS می توانیم از راهنمای موجود در github کمک بگیریم:

https://github.com/kubernetes-csi/csi-driver-nfs

در قسمت Installation آدرس بالا دو روش برای نصب بیان شده است؛ اولی نصب با Helm chart و دومی نصب با kubectl

برای نصب با روش kubectl طبق مستندات این صفحه، می توانیم دستور زیر را اجرا کنیم، با اجرای این دستور یک shell فایل دانلود شده و روی سیستم ما اجرا می شود. داخل این فایل shell فایلهای yaml مربوطه دانلود و apply می شود.

curl -skSL https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/v4.13.2/deploy/install-driver.sh | bash -s v4.13.2 --

همانگونه که در خروجی اجرا هم مشاهده می شود، پس از نصب NFS driver، یک سری resource type و deployment و پاد به کوبرنتیز اضافه می شود.

بعد از نصب CSI driver، باید storage class تعریف کرد:

nfs-storageclass.yaml

apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-csi provisioner: nfs.csi.k8s.io parameters: server: 192.168.65.43 share: /var/team9 reclaimPolicy: Delete

کوبرنتیز بصورت پیش فرض storage class مربوط به NFS را در خود دارد. همانطور که مشاهده کردید ما بدون نصب CSI driver و تعریف storage class جدید هم توانستیم PV و PVC تعریف کنیم.

نصب NFS CSI driver و نیز تعریف storage class جدید فقط جنبه آموزش داشت. همین فرآیند را می توانیم برای سایر Storage ها طی کرده و در کوبر خود از آنها استفاده کنیم.

حالا بیایید یک PVC جدید با استفاده از storage class جدیدی که خودمان برای NFS ایجاد کردیم بسازیم:

nfs-csi-pvc.yaml

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-pvc spec: accessModes: - ReadWriteMany storageClassName: nfs-csi resources: requests: storage: 5Gi

ایجاد NFS storage class جدید درست است که جنبه آموزشی داشت اما یک تفاوت با NFS storage class پیش فرض کوبرنتیز دارد و آن قابلیت Dynamic Provisioning است یعنی نیاز نیست ما ابتدا PV و بعد PVC تعریف کنیم. با تعریف PVC و اعمال آن، PV بصورت خودکار ساخته می شود.


Host Path

حالت بعدی در Storage کوبرنتیز Host Path می باشد. یعنی ما اطلاعات پاد را در مسیری از نود آن ذخیره کنیم. در این حالت اگر از یک پاد چند نسخه در نودهای مختلف اجرا شده باشد، دیتاها بصورت پراکنده در نودها ذخیره می شود و آنها را بصورت متمرکز نداریم مگر اینکه با ابزارهایی مانند FileBeat آنها را متمرکزسازی کنیم.

از این روش در برخی موارد مانند ذخیره لاگ برنامه ها استفاده می شود.


Secrets & ConfigMaps

فایلهای کانفیگ و پسوردها را اگر در سورس کد یا سرور git قرار دهیم، امکان دسترسی برنامه نویسان و سایرین به آنها وجود دارد. کوبرنتیز پیشنهاد می کند که کانفیگ ها در قالب یک آبجکت به کوبر معرفی شود، کوبر هنگام اجرای پاد همان آبجکت را در برنامه override می کند. یعنی هر چه اطلاعات و کانفیگ های حساس است را از دل برنامه خارج کنیم و آنها را در قالب Secret و ConfigMap نگه داریم.

مثال: nginx یک فایل با پسوند conf دارد که کانفیگ سرویس nginx در آن قرار می گیرد. بیایید با هم این فایل را به ConfigMap در کوبرنتیز تبدیل کنیم.

nginx.yaml

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

اگر ما فایل nginx.yaml را apply کنیم، کانفیگ nginx هم در آن وجود دارد و ما باید بعد از بالا آمدن پاد، کانفیگ آن را تنظیم کنیم. اگر پاد دیگری هم بالا بیاید باید همین کار را برای آن انجام دهیم.

راه حل این است که ما یک فایل کانفیگ مانند default.conf بسازیم و آن را داخل nginx اعمال کنیم.

default.conf

server { listen 80; server_name _; return 403; location / { root /usr/share/nginx/html; index index.html index.htm; } }
kubectl create configmap nginxconfig --from-file=default.conf --dry-run=client -o yaml > nginxconfigmap.yaml

nginxconfigmap.yaml

apiVersion: v1 data: default.conf: "server { \n listen 80;\n server_name _;\n location / {\n\n root/usr/share/nginx/html;\n index.html index.htm;\n}\n}\n\n" kind: ConfigMap metadata: name: nginxconfig

حالا کانفیگ مپ تولید شده را در کوبرنتیز اعمال می کنیم:

# kubectl apply -f nginxconfigmap.yaml configmap/nginxconfig created #kubectl get cm NAME DATA AGE kube-root-ca.crt 1 97d nginxconfig 1 2s

سپس فایل manifest سرویس nginx را به روز رسانی می کنیم تا کانفیگ خود را از کانفیگ مپ nginxconfigmap بگیرد:

nginx.yaml

apiVersion: apps/v1 kind: Deployment metadata: name: nginx labels: app: nginx spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: volumes: - name: nginx-config configMap: name: nginxconfig containers: - name: nginx image: docker.arvancloud.ir/nginx:1.17.1 imagePullPolicy: IfNotPresent volumeMounts: - name: nginx-config mountPath: /etc/nginx/conf.d/
# kubectl apply -f nginx.yaml deployment.apps/nginx created

بیایید نگاهی به داخل پاد nginx بیندازیم

همانطور که مشاهده می کنید فایل کانفیگ داخل پاد همان کانفیگی است که ما در nginxconfigmap.yaml داشتیم.

حالا برای اعمال کانفیگ جدید در nginx فقط کافی است ما فایل جدید nginxconfigmap.yaml را در کوبرنتیز apply کنیم تا کانفیگ nginx بصورت خودکار تغییر کند.

1. update default.conf file 2. kubectl create configmap nginxconfig --from-file=default.conf --dry-run=client -o yaml > nginxconfigmap.yaml 3. kubectl apply -f nginxconfigmap.yaml 4. Now nginx config updated automatically

SubPath

در مثالی که بیان شد، کل پوشه conf.d مپ شده است. یعنی همه فایلهای داخل این پوشه بروز رسانی می شود. حال اگر بخواهیم فقط فایل default.conf مپ و بروزرسانی شود، باید از SubPath استفاده کنیم. این روش فقط یک بدی دارد و آن این است که برای اعمال تغییرات configmap، پاد باید متوقف شده و مجددا بالا بیاید.

nginx.yaml

apiVersion: apps/v1 kind: Deployment metadata: name: nginx labels: app: nginx spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: volumes: - name: nginx-config configMap: name: nginxconfig containers: - name: nginx image: docker.arvancloud.ir/nginx:1.17.1 imagePullPolicy: IfNotPresent volumeMounts: - name: nginx-config mountPath: /etc/nginx/conf.d/default.conf subPath: default.conf readOnly: true

تعریف ConfigMap از روی Variable ها

nginxvars.yaml

class=devops httpurl=https://cando.ac

kubectl create configmap nginxenv --from-env-file=nginxvars.yaml --dry-run=clinet -o yaml > nginxvarconfigmap.yaml

nginxvarconfigmap.yaml

apiVersion: v1 data: class: devops httpurl: https://cando.ac kind: ConfigMap metadata: name: nginxenv

nginx.yaml

apiVersion: apps/v1 kind: Deployment metadata: name: nginx labels: app: nginx spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: volumes: - name: nginx-config configMap: name: nginxconfig containers: - name: nginx image: docker.arvancloud.ir/nginx:1.17.1 imagePullPolicy: IfNotPresent env: - name: class valueFrom: configMapKeyRef: name: nginxenv key: class - name: httpurl valueFrom: configMapKeyRef: name: nginxenv key: httpurl volumeMounts: - name: nginx-config mountPath: /etc/nginx/conf.d/default.conf subPath: default.conf readOnly: true

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