GlusterFS Kurulum ve Yapılandırma
GlusterFS-9 & Heketi-10 & Kubernets v1.25
Merhabalar,
Bu yazımda ubuntu 20.04 üzerine glusterfs, heketi ve kubernetes cluster’ımızı kurup glusterfs ile dynamic provisioning storage nasıl yapıldığını inceliycez. İlk olarak glusterfs, heketi ne olduğunu bakalım.
GlusterFS: Disk depolama kaynaklarını birden çok sunucudan tek bir küresel ad alanında toplayan, ölçeklenebilir,açık kaynaklı, dağıtılmış bir dosya sistemidir.Açık kaynaklı bir dosya sistemi olan GlusterFS, disk birimlerini Pod’lara bağlamayı sağlar. High Availability ve Disaster Recovery yapılar sunar. Fiziksel veya Bulut ortamlarında çalışabilir. HA Object Storage için Glusterfs en iyi çözümlerden biridir.
Heketi: GlusterFS disklerini yönetmek için kullanılabilecek bir RESTful tabanlı yönetim arabirimi sağlar. Glusterfs için oluşturulmuştur ve diskleri dinamik olarak yönetmeyi sağlar. Heketi üzerindeki işlemler için heketi-cli tool çalışmaktadır. heketi-cli ile API üzerinden haberleşerek çalışır kubernetes bulununan storage class’ınızı glusterfs e bağlanmasını sağlar.
GlusterFS kurulumu için en az 3 farklı VM veya fiziksel makineye ihtiyaçımız var.
Not: Best practice olarak önerilen 3 node ve üstüdür ama isterseniz 2 node’lu kurulumda yapabilirsiniz.
Glusterfs kurulumu yapmadan önce disk işlemlerini yapmamız gerekiyor. Glusterfs LVM türünde disk olmalıdır. Disk oluşturma işlemi için;
Not: Eğer LVM yüklemezseniz heketi topology oluşturmaya çalıştığınız disk hatası alırsınız.
Tüm Glusterfs kurulumu yapacağınız node’lar aşağıdaki komutları uygulamalısınız. Bu örnekte sdb disk’ini LVM yapıp glusterfs bağlanması sağlanacaktır.
sudo apt install -y lvm
sudo fdisk /dev/sdb
n
p
enter
t
L
8e
w
Kontrol için;
sudo sfdisk -l /dev/sdb
Disk işlemi bittiğine göre. Şimdide glusterfs kurulumuna geçebiliriz.
192.168.1.25 glusterfs-cluster-25 --> GlusterFS + Heketi
192.168.1.26 glusterfs-cluster-26 --> GlusterFS
192.168.1.27 glusterfs-cluster-27 --> GlusterFS
---
192.168.1.7 k8s-master-1 -> kubernetes master 1
Not: Heketi isterseniz farklı bir makineye de kurabilirsiniz.
GlusterFS kurulumu yapacağımız tüm node’ların host’una ekliyoruz. Üç node’umuz içinde aşağıdaki IP ve Hostname ekleyip , sonrasında kaydedip çıkıyoruz.
Not: Tüm glusterfs kurulumu yapacak node’ların /etc/host ‘una eklemeliyiz.
sudo vim /etc/hosts
192.168.1.25 glusterfs-cluster-25
192.168.1.26 glusterfs-cluster-26
192.168.1.27 glusterfs-cluster-27
Şimdide GlusterFS tüm nodelara kurulumu yapalım. Bunun için terminal ekranında sıra ile aşağıdaki komutları çalıştıralım.
# For Ubuntu Install
sudo apt install software-properties-common
sudo add-apt-repository ppa:gluster/glusterfs-9
sudo apt update
sudo apt install -y glusterfs-server
GlusterFS servisi başlatıp enable ettikten sonra servisin durumuna bakalım.
sudo systemctl start glusterd
sudo systemctl enable glusterd
sudo systemctl status glusterd
GlusterFS versiona bakmak için;
sudo gluster --version
GlusterFS servisini ilgili node’lara başarılı şekilde kurduğumuza göre şimdide bu node’ların birbiri ile iletişimize geçmesi için bağlanması gerekiyor. Bunun için GlusterFS ortamımızdaki main makine olan 192.168.1.25 glusterfs-cluster-25 makinesinden aşağıdaki komutları terminal ekranından çalıştırıyoruz.
sudo gluster peer probe glusterfs-cluster-26
sudo gluster peer probe glusterfs-cluster-27
Bağlantıyı kontrol etmek için;
sudo gluster peer status
Evet. Başarılı şekilde GlusterFS node’larımızı birbirine connection olmasını sağladık. Şimdide glusterfs kullanışlı komut listesine bakalım.
sudo gluster --help
sudo gluster volume help
sudo gluster volume list
Görüldüğü gibi şuan bir volume olmadığı için boş gelmektedir. volume help komutu ile yapabilceklerinizi karıştırmanızı tavsiye ederim.
Sıra geldi heketi kurmaya. Heketinin ihtiyaç duyduğu kernel paketlerini yüklemek ile başlayalım.
sudo apt -y install thin-provisioning-tools
sudo su
for i in dm_snapshot dm_mirror dm_thin_pool; do
sudo modprobe $i
done
İlk olarak heketi indirip gerekli config ayarlarını yapalım.
sudo wget https://github.com/heketi/heketi/releases/download/v10.4.0/heketi-v10.4.0-release-10.linux.amd64.tar.gz
sudo tar -xvf heketi-v10.4.0-release-10.linux.amd64.tar.gz
sudo chmod +x heketi/{heketi,heketi-cli}
sudo cp heketi/{heketi,heketi-cli} /usr/bin
sudo mkdir -p /var/lib/heketi /etc/heketi /var/log/heketi
Heketi ve heketi cli versiyonunu kontrol etmel için;
sudo heketi --version
sudo heketi-cli --version
Şimdide bir tane heketi group ve user oluşturalım.
sudo groupadd --system heketi
sudo useradd -s /sbin/nologin --system -g heketi heketi
heketi.json dosyasını ayarlayalım.
sudo cp heketi/heketi.json /etc/heketi
sudo vim /etc/heketi/heketi.json
{
"_port_comment": "Heketi Server Port Number",
"port": "8080",
"_enable_tls_comment": "Enable TLS in Heketi Server",
"enable_tls": false,
"_cert_file_comment": "Path to a valid certificate file",
"cert_file": "",
"_key_file_comment": "Path to a valid private key file",
"key_file": "",
"_use_auth": "Enable JWT authorization. Please enable for deployment",
"use_auth": false,
"_jwt": "Private keys for access",
"jwt": {
"_admin": "Admin has access to all APIs",
"admin": {
"_key_comment": "Set the admin key in the next line",
"key": "PASSWORD"
},
"_user": "User only has access to /volumes endpoint",
"user": {
"_key_comment": "Set the user key in the next line",
"key": "PASSWORD"
}
},
"_backup_db_to_kube_secret": "Backup the heketi database to a Kubernetes secret when running in Kubernetes. Default is off.",
"backup_db_to_kube_secret": false,
"_profiling": "Enable go/pprof profiling on the /debug/pprof endpoints.",
"profiling": false,
"_glusterfs_comment": "GlusterFS Configuration",
"glusterfs": {
"_executor_comment": [
"Execute plugin. Possible choices: mock, ssh",
"mock: This setting is used for testing and development.",
" It will not send commands to any node.",
"ssh: This setting will notify Heketi to ssh to the nodes.",
" It will need the values in sshexec to be configured.",
"kubernetes: Communicate with GlusterFS containers over",
" Kubernetes exec api."
],
"executor": "ssh",
"_sshexec_comment": "SSH username and private key file information",
"sshexec": {
"keyfile": "/etc/heketi/heketi_key",
"user": "root",
"port": " 22",
"fstab": "/etc/fstab"
},
"_db_comment": "Database file name",
"db": "/var/lib/heketi/heketi.db",
"_refresh_time_monitor_gluster_nodes": "Refresh time in seconds to monitor Gluster nodes",
"refresh_time_monitor_gluster_nodes": 120,
"_start_time_monitor_gluster_nodes": "Start time in seconds to monitor Gluster nodes when the heketi comes up",
"start_time_monitor_gluster_nodes": 10,
"_loglevel_comment": [
"Set log level. Choices are:",
" none, critical, error, warning, info, debug",
"Default is warning"
],
"loglevel" : "debug",
"_auto_create_block_hosting_volume": "Creates Block Hosting volumes automatically if not found or exsisting volume exhausted",
"auto_create_block_hosting_volume": true,
"_block_hosting_volume_size": "New block hosting volume will be created in size mentioned, This is considered only if auto-create is enabled.",
"block_hosting_volume_size": 500,
"_block_hosting_volume_options": "New block hosting volume will be created with the following set of options. Removing the group gluster-block option is NOT recommended. Additional options can be added next to it separated by a comma.",
"block_hosting_volume_options": "group gluster-block",
"_pre_request_volume_options": "Volume options that will be applied for all volumes created. Can be overridden by volume options in volume create request.",
"pre_request_volume_options": "",
"_post_request_volume_options": "Volume options that will be applied for all volumes created. To be used to override volume options in volume create request.",
"post_request_volume_options": ""
}
}
heketi.json da bilmemiz gerekenler;
- admin user şifresi belirlemeniz.
- heketi port 8080 ama başka port’u da verebilirsiniz.
- db loglarının tutulduğu konum
Not: Password yazan kısma kendi belirlediğiniz password girmelisiniz. Çünkü heketi-topology yüklerken gerekecektir.
Bu örnek için;
admin_password ivd7dfORN7QNeKVO
user_password gZPgdZ8NtBNj6jfp
Şimdide tüm glusterfs birbirine password sormadan bağlanması için ssh_keygen oluşturmalıyız ve bu ssh userları root user ayarı yapmak zorundasınız.
Not: Heketi adımlarını sadece heketi kurduğumuz makine de yapıyoruz.
sudo ssh-keygen -f /etc/heketi/heketi_key -t rsa -N ''
cd /etc/heketi
ll
sudo chown heketi:heketi /etc/heketi/heketi_key*
heketi group’una ekmeyide unutmuyoruz ( chown) ile.
Şimdide bu oluşturduğumuz public ssh-keygen ni tüm glusterfs node’larına authorized_keys eklemeliyiz. Bunun için;
sudo su
for i in glusterfs-cluster-25 glusterfs-cluster-26 glusterfs-cluster-27; do
ssh-copy-id -i /etc/heketi/heketi_key.pub root@$i
done
3 makine içinde yes ve sonrasında root şifrelerini girmeliyiz.
Kontrol etmek için;
sudo ssh -i /etc/heketi/heketi_key root@glusterfs-cluster-25
sudo ssh -i /etc/heketi/heketi_key root@glusterfs-cluster-26
sudo ssh -i /etc/heketi/heketi_key root@glusterfs-cluster-27
Not: Bu işlemleri yapmadan heketi servisini oluşturur ve başlatmaya çalışırsanız hata alırsınız.
Heketi env dosyasını indirelim.
sudo wget -O /etc/heketi/heketi.env https://raw.githubusercontent.com/heketi/heketi/master/extras/systemd/heketi.env
sudo chown -R heketi:heketi /var/lib/heketi /var/log/heketi /etc/heketi
Son olarak heketi service oluşturalım.
sudo vim /etc/systemd/system/heketi.service
[Unit]
Description=Heketi Server
[Service]
Type=simple
WorkingDirectory=/var/lib/heketi
EnvironmentFile=-/etc/heketi/heketi.env
User=heketi
ExecStart=/usr/bin/heketi --config=/etc/heketi/heketi.json
Restart=on-failure
StandardOutput=syslog
StandardError=syslog
[Install]
WantedBy=multi-user.target
heketi servisinde bilmemiz gerekenler;
- environment file: env file yolunu göstermeliyiz
- user: hangi user heketi config dosyalarını kullanıyorsa onu tanımlamalıyız.
Heketi servis reload, enable ve start edip bakalım.
sudo systemctl start heketi
sudo systemctl daemon-reload
sudo systemctl enable heketi
sudo systemctl status heketi
Son olarak ta heketi portunu kontrol edelim.
sudo apt update
sudo apt install -y net-tools
# Check
sudo netstat -tuplen | grep LISTEN | grep heketi
Heketi servisini test etmek için;
sudo apt update
sudo apt install -y curl
curl http://glusterfs-cluster-25:8080/hello
Evet. başarılı şekilde heketi service imizi oluşturduk. Şimdide topology oluşturalım.
sudo vim /etc/heketi/topology.json
{
"clusters": [
{
"nodes": [
{
"node": {
"hostnames": {
"manage": [
"glusterfs-cluster-25"
],
"storage": [
"192.168.1.25"
]
},
"zone": 1
},
"devices": [
"/dev/sdb1"
]
}, {
"node": {
"hostnames": {
"manage": [
"glusterfs-cluster-26"
],
"storage": [
"192.168.1.26"
]
},
"zone": 1
},
"devices": [
"/dev/sdb1"
]
}, {
"node": {
"hostnames": {
"manage": [
"glusterfs-cluster-27"
],
"storage": [
"192.168.1.27"
]
},
"zone": 1
},
"devices": [
"/dev/sdb1"
]
}
]
}
]
}
Topology config te bilmemiz gerekenler;
- GlusterFS dahil olan tüm node’ların hostname, Ip adresi ve zone türüdür.
Topology kaydedip çıktından sonra heketi user ve roups izni verelin.
sudo chown -R heketi:heketi /var/lib/heketi /var/log/heketi /etc/heketi
Topology oluşturmak için;
sudo heketi-cli topology load --user admin --secret heketi_admin_secret --json=/etc/heketi/topology.json
Not: Burada Ok görmelisiniz. Eğer disk’iniz mount olmazsa başka bir hata alırsınız.
Heketi cli-user,passwprd ve host bilgilerini bashrc ekleyelim. Bunu /etc/heketi/heketi.env altında yapabilirsiniz.
sudo vim ~/.bashrc
export HEKETI_CLI_SERVER=http://192.168.1.25:8080
export HEKETI_CLI_USER=admin
export HEKETI_CLI_KEY="ivd7dfORN7QNeKVO"
source ~/.bashrc
Heketi cli kullanışlı komutlarına bakalım. Bizim için önemli olan admin user ve clusterid. Çünkü kubernetes dynamic storage oluştururken lazım olacaktır.
heketi-cli --help
heketi-cli cluster list
ClusterID bizim için çok önemlidir.
heketi-cli node list
heketi-cli node info ID
heketi-cli topology info
heketi-cli cluster info clusterid
glusterfs servis logları;
sudo tail -f /var/log/glusterfs/glusterd.log
Son olarak heketi ile bir volume oluşturalım ve glıusterfs node’larına yazılıp yazılmadığını kontrol edelim.
heketi-cli volume create --size=1
Kontrol edelim;
heketi-cli volume list
Görüldüğü gibi glusterfs default volume “vol_” prefix ile oluşturulmuştur.
heketi-cli topology info
Görüldüğü gibi 3 glusterfs node’una volume başarılı şekilde oluşturulmuştur. Ek olarak volume list bakarsanız;
sudo gluster volume list
Şimdide hızlıca bir kubernetes cluster’ı kuralım.
OS upgrade and reboot machine
``` bash
sudo apt update
sudo apt -y full-upgrade
```
Install kubelet, kubeadm and kubectl
``` bash
sudo apt install curl apt-transport-https -y
curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg|sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/k8s.gpg
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt update
## List of package command
sudo apt list kubeadm -a
## Installation latest version command below
sudo apt install kubelet kubeadm kubectl -y
## Specific version installation
sudo apt install -y kubelet=1.25.1-00 kubeadm=1.25.1-00 kubectl=1.25.1-00
## Check version
kubelet --version
kubectl version
kubeadm version
## hold
sudo apt-mark hold kubelet kubeadm kubectl
```
##Disable Swap Space
``` bash
## Temporary
sudo swapoff -a
## Permanently
sudo vim /etc/fstab
# swapfile -> adding in front of swapfile #
## Confirm setting is correct
sudo mount -a
free -h
```
Enable kernel modules and configure sysctl
``` bash
# Enable kernel modules
sudo modprobe overlay
sudo modprobe br_netfilter
# Add some settings to sysctl
sudo tee /etc/sysctl.d/kubernetes.conf<<EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
# Reload sysctl
sudo sysctl --system
```
Install Container runtime (Master and Worker nodes)
``` bash
## Instalation containerd
# Configure persistent loading of modules
sudo tee /etc/modules-load.d/k8s.conf <<EOF
overlay
br_netfilter
EOF
# Load at runtime
sudo modprobe overlay
sudo modprobe br_netfilter
# Ensure sysctl params are set
sudo tee /etc/sysctl.d/kubernetes.conf<<EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
# Reload configs
sudo sysctl --system
# Install required packages
sudo apt install -y curl gnupg2 software-properties-common apt-transport-https ca-certificates
# Add Docker repo
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
# Install containerd
sudo apt update
sudo apt install -y containerd.io
# Configure containerd and start service
sudo su -
mkdir -p /etc/containerd
containerd config default>/etc/containerd/config.toml
# restart containerd
sudo systemctl restart containerd
sudo systemctl enable containerd
sudo systemctl status containerd
# To use the systemd cgroup driver, set plugins.cri.systemd_cgroup = true
cat /etc/containerd/config.toml | grep SystemdCgroup
sudo sed -i 's/SystemdCgroup \= false/SystemdCgroup \= true/g' /etc/containerd/config.toml
```
Initialize control plane (run on first master node)
``` bash
## br_netfilter module is loaded
lsmod | grep br_netfilter
## Enable kubelet service
sudo systemctl enable kubelet
## Pull container images
sudo kubeadm config images pull
## If you have multiple CRI sockets, please use --cri-socket to select one:
## Containerd
sudo kubeadm config images pull --cri-socket /run/containerd/containerd.sock
## Bootstrap with shared endpoint (DNS name for control plane API)
sudo vim /etc/hosts
IP_Addreses Dns_Name
192.168.1.7 kubernetes.glusterfs.deniz.master.internal
192.168.1.7 k8s-master-1
# Create cluster
sudo kubeadm init --control-plane-endpoint="kubernetes.glusterfs.deniz.master.internal:6443" --apiserver-advertise-address=192.168.1.7 --node-name k8s-master-1 --pod-network-cidr=192.168.0.0/24
# To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Kubernetes master node’umuzu kontrol edelim.
kubectl get nodes -owide
Conatainer network interface yükleyelim. (WeaveNet )
kubectl apply -f https://github.com/weaveworks/weave/releases/download/v2.8.1/weave-daemonset-k8s.yaml
CNI yükledikten sonra tekrar kontrol ettiğimizde;
kubectl get nodes -owide
Başarılı şekilde kubernetes cluster’ımız kurulumu gerçekleştirdik. Glusterfs var olan kubernetes cluster’ımıza static ve dynamic olarak bağlayabiliriz. Biz burada dynamic provision yapacağız.
Ilk olarak heketi cluster’ımızı kontrol edelim.
heketi-cli cluster list
Tüm kubernetes nodelarına master ve worker glusterfs-client paketini kuruyoruz.
sudo apt-get install software-properties-common
sudo add-apt-repository ppa:gluster/glusterfs-9
sudo apt install glusterfs-client -y
Not: Glusterfs versiyonu ne ise glusterfs-client versiyonu aynı olmaldır.
Şimdide bir tane k8s-secret oluşturalım. Secret bildiğiniz gibi base64 formatında key-value ekleyebilirsiniz. Bunun için /etc/heketi/heketi.json dosyasındaki admin password’unu base64 türünde eklemelisiniz.
echo -n "PASSWORD" | base64
#For this example,
echo -n "ivd7dfORN7QNeKVO" | base64
#Check
echo -n "aXZkN2RmT1JON1FOZUtWTw==" | base64 -d
apiVersion: v1
kind: Secret
metadata:
name: heketi-secret
namespace: default
type: "kubernetes.io/glusterfs"
data:
# echo -n "ivd7dfORN7QNeKVO" | base64
key: aXZkN2RmT1JON1FOZUtWTw==
type: kubernetes.io/glusterfs
kubectl apply -f gluster-heketi-secret.yaml
kubectl get secret
Görüldüğü gibi secret oluşturduk. Şimdide Storage oluşturalım.
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: gluster-heketi
provisioner: kubernetes.io/glusterfs
reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: true
parameters:
resturl: "http://192.168.1.25:8080"
restuser: "admin"
secretName: "heketi-secret"
secretNamespace: "default"
volumetype: "replicate:2"
volumenameprefix: "k8s-dev"
clusterid: "c4bf3993b3c7ef4c98ca73006620770a"
---
Storage.yaml bilmemiz gerekenler;
- resturl: heketi kurulu olduğ makine IP:Port
- restuser: heketi.json içersindeki user
- volumetype: kaç replica tutulacağı
- volumenameprefix: oluşturulan volume prefix
- clusterid: En önemlisi clusterid dir. “heketi-cli cluster list” baktığınızda gelen unique id dir.
kubectl apply -f gluster-heketi-storage.yaml
kubectl get sc
Son olarakta bir tane persistent volume claims oluşturalım.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gluster-pvc
annotations:
volume.beta.kubernetes.io/storage-class: gluster-heketi
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
kubectl apply -f gluster-heketi-pvc.yaml
kubectl get pvc
kubectl get pv
Evet görüldüğü gibi pvc bound oldu. Şimdide bir tane pod oluşturalım. Sonrasında glusterfs node’larmızdan kontrol edelim.
apiVersion: v1
kind: Pod
metadata:
name: gluster-pod
labels:
name: gluster-pod
spec:
containers:
- name: gluster-pod
image: busybox
command: ["sleep", "60000"]
volumeMounts:
- name: gluster-vol
mountPath: /usr/share/busybox
readOnly: false
volumes:
- name: gluster-vol
persistentVolumeClaim:
claimName: gluster-pvc
kubectl apply -f gluster-heketi-pod.yaml
kubectl get pods
Evet görüldüğü gibi pod’umuzda persistent volume claims bağlandı. Son olarak glusterfs kontrol edelim.
sudo gluster volume list
Baktığımızda gördüğünüz gibi bizim volume-prefix’imiz alan bir tane volume oluşturulmuş.
sudo gluster volume info
Son dedim ama son bir şey daha pod’un içerisine girerek pvc bağladığımız /usr/share/busybox yere test isminde bir dosya oluşturup içerisine rastgele bir şey yazalım.
kubectl get pods
kubectl exec -it gluster-pod -- sh
Şimdide glusterfs yüklü olduğu 192.168.1.25 makinesine gidelim.
sudo gluster volume info
Evet görüldüğü gibi biz replicayı storageclass objemizde 2 olarak ayarladığımız için 2 node yazmış.
Brick1: 192.168.1.25:/var/lib/heketi/mounts/vg_c272f5ab5dad8e2f4d5f0f90f3e60ab4/brick_61dabf5d22a792b100ac53bf22ca66bf/brick
İlgili folder’a gittiğimizde;
sudo su
cd /var/lib/heketi/mounts/vg_c272f5ab5dad8e2f4d5f0f90f3e60ab4/brick_61dabf5d22a792b100ac53bf22ca66bf/brick
cat test
Başarılı şekilde state sağlamış olduk. Github kurulum dosyaları buradan ulaşabilirsiniz.
Bu yazımızın da sonuna gelmiş bulunmaktayız. Araştırmalarım ve sektörde karşılaştığım senaryolar üzerine yazılarımı yazmaya devam edeceğim. Umarım faydalı bir yazı olmuştur. Yazımı okuduğunuz için teşekkürler.
Referanslar;