Container Storage Interface (CSI) β ΡΡΠΎ ΡΠ½ΠΈΡΠΈΡΠΈΡΠΎΠ²Π°Π½Π½ΡΠΉ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΈΡ Kubernetes ΠΈ ΡΠΈΡΡΠ΅ΠΌ Ρ
ΡΠ°Π½Π΅Π½ΠΈΡ Π΄Π°Π½Π½ΡΡ
. ΠΠΊΡΠ°ΡΡΠ΅ ΠΎ Π½ΡΠΌ ΠΌΡ ΡΠΆΠ΅
Π ΡΡΠ°ΡΡΠ΅ ΠΏΡΠΈΠ²Π΅Π΄Π΅Π½Ρ ΡΠ΅Π°Π»ΡΠ½ΡΠ΅, Ρ
ΠΎΡΡ ΠΈ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ ΡΠΏΡΠΎΡΡΠ½Π½ΡΠ΅ Π΄Π»Ρ ΡΠ΄ΠΎΠ±ΡΡΠ²Π° Π²ΠΎΡΠΏΡΠΈΡΡΠΈΡ ΠΏΡΠΈΠΌΠ΅ΡΡ. Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΡ ΠΈ Π½Π°ΡΡΡΠΎΠΉΠΊΡ ΠΊΠ»Π°ΡΡΠ΅ΡΠΎΠ² Ceph ΠΈ Kubernetes Π½Π΅ ΡΠ°ΡΡΠΌΠ°ΡΡΠΈΠ²Π°Π΅ΠΌ.
ΠΠ°ΠΌ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠ½ΠΎ, ΠΊΠ°ΠΊ ΡΡΠΎ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ?
ΠΡΠ°ΠΊ, Ρ Π²Π°Ρ ΠΏΠΎΠ΄ ΡΡΠΊΠΎΠΉ Π΅ΡΡΡ ΠΊΠ»Π°ΡΡΠ΅Ρ Kubernetes, ΡΠ°Π·Π²Π΅ΡΠ½ΡΡΡΠΉ, ΠΊ ΠΏΡΠΈΠΌΠ΅ΡΡ,
ΠΡΠ»ΠΈ Π²ΡΡ ΡΡΠΎ Ρ Π²Π°Ρ Π΅ΡΡΡ, ΠΏΠΎΠ΅Ρ Π°Π»ΠΈ!
Π‘Π½Π°ΡΠ°Π»Π° Π·Π°ΠΉΠ΄Π΅ΠΌ Π½Π° ΠΎΠ΄Π½Ρ ΠΈΠ· Π½ΠΎΠ΄ ΠΊΠ»Π°ΡΡΠ΅ΡΠ° Ceph ΠΈ ΠΏΡΠΎΠ²Π΅ΡΡΠ΅ΠΌ, ΡΡΠΎ Π²ΡΡ Π² ΠΏΠΎΡΡΠ΄ΠΊΠ΅:
ceph health
ceph -s
ΠΠ°Π»Π΅Π΅ ΡΡΡ ΠΆΠ΅ ΡΠΎΠ·Π΄Π°Π΄ΠΈΠΌ ΠΏΡΠ» Π΄Π»Ρ RBD Π΄ΠΈΡΠΊΠΎΠ²:
ceph osd pool create kube 32
ceph osd pool application enable kube rbd
ΠΠ΅ΡΠ΅Ρ
ΠΎΠ΄ΠΈΠΌ Π² ΠΊΠ»Π°ΡΡΠ΅Ρ Kubernetes. Π’Π°ΠΌ ΠΏΠ΅ΡΠ²ΡΠΌ Π΄Π΅Π»ΠΎΠΌ ΡΡΡΠ°Π½ΠΎΠ²ΠΈΠΌ Ceph CSI Π΄ΡΠ°ΠΉΠ²Π΅Ρ Π΄Π»Ρ RBD. Π‘ΡΠ°Π²ΠΈΡΡ Π±ΡΠ΄Π΅ΠΌ, ΠΊΠ°ΠΊ ΠΈ ΠΏΠΎΠ»ΠΎΠΆΠ΅Π½ΠΎ, ΡΠ΅ΡΠ΅Π· Helm.
ΠΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ ΡΠ΅ΠΏΠΎΠ·ΠΈΡΠΎΡΠΈΠΉ Ρ ΡΠ°ΡΡΠΎΠΌ, ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ Π½Π°Π±ΠΎΡ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ
ΡΠ°ΡΡΠ° ceph-csi-rbd:
helm repo add ceph-csi https://ceph.github.io/csi-charts
helm inspect values ceph-csi/ceph-csi-rbd > cephrbd.yml
Π’Π΅ΠΏΠ΅ΡΡ Π½ΡΠΆΠ½ΠΎ Π·Π°ΠΏΠΎΠ»Π½ΠΈΡΡ ΡΠ°ΠΉΠ» cephrbd.yml. ΠΠ»Ρ ΡΡΠΎΠ³ΠΎ ΡΠ·Π½Π°Π΅ΠΌ ID ΠΊΠ»Π°ΡΡΠ΅ΡΠ° ΠΈ IP-Π°Π΄ΡΠ΅ΡΠ° ΠΌΠΎΠ½ΠΈΡΠΎΡΠΎΠ² Π² Ceph:
ceph fsid # ΡΠ°ΠΊ ΠΌΡ ΡΠ·Π½Π°Π΅ΠΌ clusterID
ceph mon dump # Π° ΡΠ°ΠΊ ΡΠ²ΠΈΠ΄ΠΈΠΌ IP-Π°Π΄ΡΠ΅ΡΠ° ΠΌΠΎΠ½ΠΈΡΠΎΡΠΎΠ²
ΠΠΎΠ»ΡΡΠ΅Π½Π½ΡΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΡ Π·Π°Π½ΠΎΡΠΈΠΌ Π² ΡΠ°ΠΉΠ» cephrbd.yml. ΠΠΎΠΏΡΡΠ½ΠΎ Π²ΠΊΠ»ΡΡΠ°Π΅ΠΌ ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΠΎΠ»ΠΈΡΠΈΠΊ PSP (Pod Security Policies). ΠΠΏΡΠΈΠΈ Π² ΡΠ°Π·Π΄Π΅Π»Π°Ρ nodeplugin ΠΈ provisioner ΡΠΆΠ΅ Π΅ΡΡΡ Π² ΡΠ°ΠΉΠ»Π΅, ΠΈΡ ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΡΠ°Π²ΠΈΡΡ ΡΠ°ΠΊ, ΠΊΠ°ΠΊ ΠΏΠΎΠΊΠ°Π·Π°Π½ΠΎ Π½ΠΈΠΆΠ΅:
csiConfig:
- clusterID: "bcd0d202-fba8-4352-b25d-75c89258d5ab"
monitors:
- "v2:172.18.8.5:3300/0,v1:172.18.8.5:6789/0"
- "v2:172.18.8.6:3300/0,v1:172.18.8.6:6789/0"
- "v2:172.18.8.7:3300/0,v1:172.18.8.7:6789/0"
nodeplugin:
podSecurityPolicy:
enabled: true
provisioner:
podSecurityPolicy:
enabled: true
ΠΠ°Π»Π΅Π΅ Π²ΡΡ ΡΡΠΎ Π½Π°ΠΌ ΠΎΡΡΠ°ΡΡΡΡ β ΡΡΡΠ°Π½ΠΎΠ²ΠΈΡΡ ΡΠ°ΡΡ Π² ΠΊΠ»Π°ΡΡΠ΅Ρ Kubernetes.
helm upgrade -i ceph-csi-rbd ceph-csi/ceph-csi-rbd -f cephrbd.yml -n ceph-csi-rbd --create-namespace
ΠΡΠ»ΠΈΡΠ½ΠΎ, RBD Π΄ΡΠ°ΠΉΠ²Π΅Ρ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ!
Π‘ΠΎΠ·Π΄Π°Π΄ΠΈΠΌ Π² Kubernetes Π½ΠΎΠ²ΡΠΉ StorageClass. ΠΠ»Ρ ΡΡΠΎΠ³ΠΎ ΡΠ½ΠΎΠ²Π° ΠΏΠΎΡΡΠ΅Π±ΡΠ΅ΡΡΡ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ ΠΏΠΎΡΠ°Π±ΠΎΡΠ°ΡΡ Ρ Ceph.
Π‘ΠΎΠ·Π΄Π°ΡΠΌ Π½ΠΎΠ²ΠΎΠ³ΠΎ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ Π² Ceph ΠΈ Π²ΡΠ΄Π°ΡΠΌ Π΅ΠΌΡ ΠΏΡΠ°Π²Π° Π½Π° Π·Π°ΠΏΠΈΡΡ Π² ΠΏΡΠ» kube:
ceph auth get-or-create client.rbdkube mon 'profile rbd' osd 'profile rbd pool=kube'
Π ΡΠ΅ΠΏΠ΅ΡΡ ΠΏΠΎΡΠΌΠΎΡΡΠΈΠΌ ΠΊΠ»ΡΡ Π΄ΠΎΡΡΡΠΏΠ° Π²ΡΡ ΡΠ°ΠΌ ΠΆΠ΅:
ceph auth get-key client.rbdkube
ΠΠΎΠΌΠ°Π½Π΄Π° Π²ΡΠ΄Π°ΡΡ Π½Π΅ΡΡΠΎ ΠΏΠΎΠ΄ΠΎΠ±Π½ΠΎΠ΅:
AQCO9NJbhYipKRAAMqZsnqqS/T8OYQX20xIa9A==
ΠΠ°Π½Π΅ΡΡΠΌ ΡΡΠΎ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ Π² Secret Π² ΠΊΠ»Π°ΡΡΠ΅ΡΠ΅ Kubernetes β ΡΡΠ΄Π°, Π³Π΄Π΅ Π½ΡΠΆΠ΅Π½ userKey:
---
apiVersion: v1
kind: Secret
metadata:
name: csi-rbd-secret
namespace: ceph-csi-rbd
stringData:
# ΠΠ½Π°ΡΠ΅Π½ΠΈΡ ΠΊΠ»ΡΡΠ΅ΠΉ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡ ΠΈΠΌΠ΅Π½ΠΈ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ ΠΈ Π΅Π³ΠΎ ΠΊΠ»ΡΡΡ, ΠΊΠ°ΠΊ ΡΠΊΠ°Π·Π°Π½ΠΎ Π²
# ΠΊΠ»Π°ΡΡΠ΅ΡΠ΅ Ceph. ID ΡΠ·Π΅ΡΠ° Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΈΠΌΠ΅ΡΡ Π΄ΠΎΡΡΡΠΏ ΠΊ ΠΏΡΠ»Ρ,
# ΡΠΊΠ°Π·Π°Π½Π½ΠΎΠΌΡ Π² storage class
userID: rbdkube
userKey: <user-key>
Π ΡΠΎΠ·Π΄Π°ΡΠΌ Π½Π°Ρ ΡΠ΅ΠΊΡΠ΅Ρ:
kubectl apply -f secret.yaml
ΠΠ°Π»Π΅Π΅ Π½Π°ΠΌ Π½ΡΠΆΠ΅Π½ ΠΏΡΠΈΠΌΠ΅ΡΠ½ΠΎ ΡΠ°ΠΊΠΎΠΉ ΠΌΠ°Π½ΠΈΡΠ΅ΡΡ StorageClass:
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-rbd-sc
provisioner: rbd.csi.ceph.com
parameters:
clusterID: <cluster-id>
pool: kube
imageFeatures: layering
# ΠΡΠΈ ΡΠ΅ΠΊΡΠ΅ΡΡ Π΄ΠΎΠ»ΠΆΠ½Ρ ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΡ Π΄Π°Π½Π½ΡΠ΅ Π΄Π»Ρ Π°Π²ΡΠΎΡΠΈΠ·Π°ΡΠΈΠΈ
# Π² Π²Π°Ρ ΠΏΡΠ».
csi.storage.k8s.io/provisioner-secret-name: csi-rbd-secret
csi.storage.k8s.io/provisioner-secret-namespace: ceph-csi-rbd
csi.storage.k8s.io/controller-expand-secret-name: csi-rbd-secret
csi.storage.k8s.io/controller-expand-secret-namespace: ceph-csi-rbd
csi.storage.k8s.io/node-stage-secret-name: csi-rbd-secret
csi.storage.k8s.io/node-stage-secret-namespace: ceph-csi-rbd
csi.storage.k8s.io/fstype: ext4
reclaimPolicy: Delete
allowVolumeExpansion: true
mountOptions:
- discard
ΠΡΠΆΠ½ΠΎ Π·Π°ΠΏΠΎΠ»Π½ΠΈΡΡ clusterID, ΠΊΠΎΡΠΎΡΡΠΉ ΠΌΡ ΡΠΆΠ΅ ΡΠ·Π½Π°Π»ΠΈ ΠΊΠΎΠΌΠ°Π½Π΄ΠΎΠΉ ceph fsid, ΠΈ ΠΏΡΠΈΠΌΠ΅Π½ΠΈΡΡ ΡΡΠΎΡ ΠΌΠ°Π½ΠΈΡΠ΅ΡΡ Π² ΠΊΠ»Π°ΡΡΠ΅ΡΠ΅ Kubernetes:
kubectl apply -f storageclass.yaml
Π§ΡΠΎΠ±Ρ ΠΏΡΠΎΠ²Π΅ΡΠΈΡΡ ΡΠ°Π±ΠΎΡΡ ΠΊΠ»Π°ΡΡΠ΅ΡΠΎΠ² Π² ΡΠ²ΡΠ·ΠΊΠ΅, ΡΠΎΠ·Π΄Π°Π΄ΠΈΠΌ Π²ΠΎΡ ΡΠ°ΠΊΠΎΠΉ PVC (Persistent Volume Claim):
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: rbd-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: csi-rbd-sc
Π‘ΡΠ°Π·Ρ ΠΏΠΎΡΠΌΠΎΡΡΠΈΠΌ, ΠΊΠ°ΠΊ Kubernetes ΡΠΎΠ·Π΄Π°Π» Π² Ceph Π·Π°ΠΏΡΠΎΡΠ΅Π½Π½ΡΠΉ ΡΠΎΠΌ:
kubectl get pvc
kubectl get pv
ΠΡΠΎΠ΄Π΅ Π±Ρ Π²ΡΡ ΠΎΡΠ»ΠΈΡΠ½ΠΎ! Π ΠΊΠ°ΠΊ ΡΡΠΎ Π²ΡΠ³Π»ΡΠ΄ΠΈΡ Π½Π° ΡΡΠΎΡΠΎΠ½Π΅ Ceph?
ΠΠΎΠ»ΡΡΠ°Π΅ΠΌ ΡΠΏΠΈΡΠΎΠΊ ΡΠΎΠΌΠΎΠ² Π² ΠΏΡΠ»Π΅ ΠΈ ΠΏΡΠΎΡΠΌΠ°ΡΡΠΈΠ²Π°Π΅ΠΌ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ ΠΎ Π½Π°ΡΠ΅ΠΌ ΡΠΎΠΌΠ΅:
rbd ls -p kube
rbd -p kube info csi-vol-eb3d257d-8c6c-11ea-bff5-6235e7640653 # ΡΡΡ, ΠΊΠΎΠ½Π΅ΡΠ½ΠΎ ΠΆΠ΅, Π±ΡΠ΄Π΅Ρ Π΄ΡΡΠ³ΠΎΠΉ ID ΡΠΎΠΌΠ°, ΠΊΠΎΡΠΎΡΡΠΉ Π²ΡΠ΄Π°Π»Π° ΠΏΡΠ΅Π΄ΡΠ΄ΡΡΠ°Ρ ΠΊΠΎΠΌΠ°Π½Π΄Π°
Π’Π΅ΠΏΠ΅ΡΡ Π΄Π°Π²Π°ΠΉΡΠ΅ ΠΏΠΎΡΠΌΠΎΡΡΠΈΠΌ, ΠΊΠ°ΠΊ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ ΡΠ°Π·ΠΌΠ΅ΡΠ° ΡΠΎΠΌΠ° RBD.
ΠΠ·ΠΌΠ΅Π½ΡΠ΅ΠΌ ΡΠ°Π·ΠΌΠ΅Ρ ΡΠΎΠΌΠ° Π² ΠΌΠ°Π½ΠΈΡΠ΅ΡΡΠ΅ pvc.yaml Π΄ΠΎ 2Gi ΠΈ ΠΏΡΠΈΠΌΠ΅Π½ΡΠ΅ΠΌ Π΅Π³ΠΎ:
kubectl apply -f pvc.yaml
ΠΠΎΠ΄ΠΎΠΆΠ΄ΡΠΌ, ΠΏΠΎΠΊΠ° ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ Π²ΡΡΡΠΏΡΡ Π² ΡΠΈΠ»Ρ, ΠΈ Π΅ΡΡ ΡΠ°Π· ΠΏΠΎΡΠΌΠΎΡΡΠΈΠΌ Π½Π° ΡΠ°Π·ΠΌΠ΅Ρ ΡΠΎΠΌΠ°.
rbd -p kube info csi-vol-eb3d257d-8c6c-11ea-bff5-6235e7640653
kubectl get pv
kubectl get pvc
ΠΠΈΠ΄ΠΈΠΌ, ΡΡΠΎ ΡΠ°Π·ΠΌΠ΅Ρ Ρ PVC Π½Π΅ ΠΈΠ·ΠΌΠ΅Π½ΠΈΠ»ΡΡ. Π§ΡΠΎΠ±Ρ ΡΠ·Π½Π°ΡΡ ΠΏΡΠΈΡΠΈΠ½Ρ, ΠΌΠΎΠΆΠ½ΠΎ Π·Π°ΠΏΡΠΎΡΠΈΡΡ Ρ Kubernetes ΠΎΠΏΠΈΡΠ°Π½ΠΈΠ΅ PVC Π² ΡΠΎΡΠΌΠ°ΡΠ΅ YAML:
kubectl get pvc rbd-pvc -o yaml
Π Π²ΠΎΡ ΠΈ ΠΏΡΠΎΠ±Π»Π΅ΠΌΠ°:
message: Waiting for user to (re-)start a pod to finish file system resize of volume on node. type: FileSystemResizePending
Π’ΠΎ Π΅ΡΡΡ Π΄ΠΈΡΠΊ ΡΠ²Π΅Π»ΠΈΡΠΈΠ»ΡΡ, Π° ΡΠ°ΠΉΠ»ΠΎΠ²Π°Ρ ΡΠΈΡΡΠ΅ΠΌΠ° Π½Π° Π½ΡΠΌ β Π½Π΅Ρ.
Π§ΡΠΎΠ±Ρ ΡΠ²Π΅Π»ΠΈΡΠΈΡΡ ΡΠ°ΠΉΠ»ΠΎΠ²ΡΡ ΡΠΈΡΡΠ΅ΠΌΡ, Π½Π°Π΄ΠΎ ΡΠΌΠΎΠ½ΡΠΈΡΠΎΠ²Π°ΡΡ ΡΠΎΠΌ. Π£ Π½Π°Ρ ΠΆΠ΅ ΡΠΎΠ·Π΄Π°Π½Π½ΡΠΉ PVC/PV ΡΠ΅ΠΉΡΠ°Ρ Π½ΠΈΠΊΠ°ΠΊ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ.
ΠΠΎΠΆΠ΅ΠΌ ΡΠΎΠ·Π΄Π°ΡΡ ΡΠ΅ΡΡΠΎΠ²ΡΠΉ Pod, Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ Π²ΠΎΡ ΡΠ°ΠΊ:
---
apiVersion: v1
kind: Pod
metadata:
name: csi-rbd-demo-pod
spec:
containers:
- name: web-server
image: nginx:1.17.6
volumeMounts:
- name: mypvc
mountPath: /data
volumes:
- name: mypvc
persistentVolumeClaim:
claimName: rbd-pvc
readOnly: false
Π ΡΠ΅ΠΏΠ΅ΡΡ ΠΏΠΎΡΠΌΠΎΡΡΠΈΠΌ Π½Π° PVC:
kubectl get pvc
Π Π°Π·ΠΌΠ΅Ρ ΠΈΠ·ΠΌΠ΅Π½ΠΈΠ»ΡΡ, Π²ΡΡ Π² ΠΏΠΎΡΡΠ΄ΠΊΠ΅.
Π ΠΏΠ΅ΡΠ²ΠΎΠΉ ΡΠ°ΡΡΠΈ ΠΌΡ ΡΠ°Π±ΠΎΡΠ°Π»ΠΈ Ρ Π±Π»ΠΎΡΠ½ΡΠΌ ΡΡΡΡΠΎΠΉΡΡΠ²ΠΎΠΌ RBD (ΠΎΠ½ΠΎ ΡΠ°ΠΊ ΠΈ ΡΠ°ΡΡΠΈΡΡΠΎΠ²ΡΠ²Π°Π΅ΡΡΡ β Rados Block Device), Π½ΠΎ ΡΠ°ΠΊ Π½Π΅Π»ΡΠ·Ρ Π΄Π΅Π»Π°ΡΡ, Π΅ΡΠ»ΠΈ ΡΡΠ΅Π±ΡΠ΅ΡΡΡ ΠΎΠ΄Π½ΠΎΠ²ΡΠ΅ΠΌΠ΅Π½Π½Π°Ρ ΡΠ°Π±ΠΎΡΠ° Ρ ΡΡΠΈΠΌ Π΄ΠΈΡΠΊΠΎΠΌ ΡΠ°Π·Π½ΡΡ
ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡΠΎΠ². ΠΠ»Ρ ΡΠ°Π±ΠΎΡΡ Ρ ΡΠ°ΠΉΠ»Π°ΠΌΠΈ, Π° Π½Π΅ Ρ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ Π΄ΠΈΡΠΊΠ°, Π½Π°ΠΌΠ½ΠΎΠ³ΠΎ Π»ΡΡΡΠ΅ ΠΏΠΎΠ΄Ρ
ΠΎΠ΄ΠΈΡ CephFS.
ΠΠ° ΠΏΡΠΈΠΌΠ΅ΡΠ΅ ΠΊΠ»Π°ΡΡΠ΅ΡΠΎΠ² Ceph ΠΈ Kubernetes Π½Π°ΡΡΡΠΎΠΈΠΌ CSI ΠΈ ΠΎΡΡΠ°Π»ΡΠ½ΡΠ΅ Π½Π΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΡΠ΅ ΡΡΡΠ½ΠΎΡΡΠΈ Π΄Π»Ρ ΡΠ°Π±ΠΎΡΡ Ρ CephFS.
ΠΠΎΠ»ΡΡΠΈΠΌ Π·Π½Π°ΡΠ΅Π½ΠΈΡ ΠΈΠ· Π½ΡΠΆΠ½ΠΎΠ³ΠΎ Π½Π°ΠΌ Π½ΠΎΠ²ΠΎΠ³ΠΎ Helm-ΡΠ°ΡΡΠ°:
helm inspect values ceph-csi/ceph-csi-cephfs > cephfs.yml
Π‘Π½ΠΎΠ²Π° Π½ΡΠΆΠ½ΠΎ Π·Π°ΠΏΠΎΠ»Π½ΠΈΡΡ ΡΠ°ΠΉΠ» cephfs.yml. ΠΠ°ΠΊ ΠΈ ΡΠ°Π½Π΅Π΅, ΠΏΠΎΠΌΠΎΠ³ΡΡ ΠΊΠΎΠΌΠ°Π½Π΄Ρ Ceph:
ceph fsid
ceph mon dump
ΠΠ°ΠΏΠΎΠ»Π½ΡΠ΅ΠΌ ΡΠ°ΠΉΠ» ΡΠΎ Π·Π½Π°ΡΠ΅Π½ΠΈΡΠΌΠΈ ΠΏΡΠΈΠΌΠ΅ΡΠ½ΠΎ ΡΠ°ΠΊ:
csiConfig:
- clusterID: "bcd0d202-fba8-4352-b25d-75c89258d5ab"
monitors:
- "172.18.8.5:6789"
- "172.18.8.6:6789"
- "172.18.8.7:6789"
nodeplugin:
httpMetrics:
enabled: true
containerPort: 8091
podSecurityPolicy:
enabled: true
provisioner:
replicaCount: 1
podSecurityPolicy:
enabled: true
ΠΠ±ΡΠ°ΡΠΈΡΠ΅ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅, ΡΡΠΎ Π°Π΄ΡΠ΅ΡΠ° ΠΌΠΎΠ½ΠΈΡΠΎΡΠΎΠ² ΡΠΊΠ°Π·ΡΠ²Π°ΡΡΡΡ Π² ΠΏΡΠΎΡΡΠΎΠΉ ΡΠΎΡΠΌΠ΅ address:port. ΠΠ»Ρ ΠΌΠΎΠ½ΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ cephfs Π½Π° ΡΠ·Π»Π΅ ΡΡΠΈ Π°Π΄ΡΠ΅ΡΠ° ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡΡΡ Π² ΠΌΠΎΠ΄ΡΠ»Ρ ΡΠ΄ΡΠ°, ΠΊΠΎΡΠΎΡΡΠΉ Π΅ΡΡ Π½Π΅ ΡΠΌΠ΅Π΅Ρ ΡΠ°Π±ΠΎΡΠ°ΡΡ Ρ ΠΏΡΠΎΡΠΎΠΊΠΎΠ»ΠΎΠΌ ΠΌΠΎΠ½ΠΈΡΠΎΡΠΎΠ² v2.
ΠΠΎΡΡ Π΄Π»Ρ httpMetrics (ΡΡΠ΄Π° Π±ΡΠ΄Π΅Ρ Ρ
ΠΎΠ΄ΠΈΡΡ Prometheus Π·Π° ΠΌΠ΅ΡΡΠΈΠΊΠ°ΠΌΠΈ Π΄Π»Ρ ΠΌΠΎΠ½ΠΈΡΠΎΡΠΈΠ½Π³Π°) ΠΌΡ ΠΌΠ΅Π½ΡΠ΅ΠΌ Π΄Π»Ρ ΡΠΎΠ³ΠΎ, ΡΡΠΎΠ±Ρ ΠΎΠ½ Π½Π΅ ΠΊΠΎΠ½ΡΠ»ΠΈΠΊΡΠΎΠ²Π°Π» Ρ nginx-proxy, ΠΊΠΎΡΠΎΡΡΠΉ ΡΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°Π΅ΡΡΡ KubesprayβΠ΅ΠΌ. ΠΠ°ΠΌ ΡΡΠΎ, Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ, Π½Π΅ ΠΏΠΎΡΡΠ΅Π±ΡΠ΅ΡΡΡ.
Π£ΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°Π΅ΠΌ Helm-ΡΠ°ΡΡ Π² ΠΊΠ»Π°ΡΡΠ΅Ρ Kubernetes:
helm upgrade -i ceph-csi-cephfs ceph-csi/ceph-csi-cephfs -f cephfs.yml -n ceph-csi-cephfs --create-namespace
ΠΠ΅ΡΠ΅Ρ ΠΎΠ΄ΠΈΠΌ ΠΊ Ρ ΡΠ°Π½ΠΈΠ»ΠΈΡΡ Π΄Π°Π½Π½ΡΡ Ceph, ΡΡΠΎΠ±Ρ ΡΠΎΠ·Π΄Π°ΡΡ ΡΠ°ΠΌ ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎΠ³ΠΎ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ. Π Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ ΡΠΊΠ°Π·Π°Π½ΠΎ, ΡΡΠΎ ΠΏΡΠΎΠ²ΠΈΠ·ΠΈΠΎΠ½Π΅ΡΡ CephFS Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΡ ΠΏΡΠ°Π²Π° Π΄ΠΎΡΡΡΠΏΠ° Π°Π΄ΠΌΠΈΠ½ΠΈΡΡΡΠ°ΡΠΎΡΠ° ΠΊΠ»Π°ΡΡΠ΅ΡΠ°. ΠΠΎ ΠΌΡ ΡΠΎΠ·Π΄Π°Π΄ΠΈΠΌ ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎΠ³ΠΎ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ fs Ρ ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½Π½ΡΠΌΠΈ ΠΏΡΠ°Π²Π°ΠΌΠΈ:
ceph auth get-or-create client.fs mon 'allow r' mgr 'allow rw' mds 'allow rws' osd 'allow rw pool=cephfs_data, allow rw pool=cephfs_metadata'
Π ΡΡΠ°Π·Ρ ΠΆΠ΅ ΠΏΠΎΡΠΌΠΎΡΡΠΈΠΌ Π΅Π³ΠΎ ΠΊΠ»ΡΡ Π΄ΠΎΡΡΡΠΏΠ°, ΠΎΠ½ Π½Π°ΠΌ ΠΏΡΠΈΠ³ΠΎΠ΄ΠΈΡΡΡ Π΄Π°Π»Π΅Π΅:
ceph auth get-key client.fs
Π‘ΠΎΠ·Π΄Π°Π΄ΠΈΠΌ ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠ΅ Secret ΠΈ StorageClass.
ΠΠΈΡΠ΅Π³ΠΎ Π½ΠΎΠ²ΠΎΠ³ΠΎ, ΠΌΡ ΡΡΠΎ ΡΠΆΠ΅ Π²ΠΈΠ΄Π΅Π»ΠΈ Π½Π° ΠΏΡΠΈΠΌΠ΅ΡΠ΅ RBD:
---
apiVersion: v1
kind: Secret
metadata:
name: csi-cephfs-secret
namespace: ceph-csi-cephfs
stringData:
# ΠΠ΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΠΎ Π΄Π»Ρ Π΄ΠΈΠ½Π°ΠΌΠΈΡΠ΅ΡΠΊΠΈ ΡΠΎΠ·Π΄Π°Π²Π°Π΅ΠΌΡΡ
ΡΠΎΠΌΠΎΠ²
adminID: fs
adminKey: <Π²ΡΠ²ΠΎΠ΄ ΠΏΡΠ΅Π΄ΡΠ΄ΡΡΠ΅ΠΉ ΠΊΠΎΠΌΠ°Π½Π΄Ρ>
ΠΡΠΈΠΌΠ΅Π½ΡΠ΅ΠΌ ΠΌΠ°Π½ΠΈΡΠ΅ΡΡ:
kubectl apply -f secret.yaml
Π ΡΠ΅ΠΏΠ΅ΡΡ β ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΉ StorageClass:
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-cephfs-sc
provisioner: cephfs.csi.ceph.com
parameters:
clusterID: <cluster-id>
# ΠΠΌΡ ΡΠ°ΠΉΠ»ΠΎΠ²ΠΎΠΉ ΡΠΈΡΡΠ΅ΠΌΡ CephFS, Π² ΠΊΠΎΡΠΎΡΠΎΠΉ Π±ΡΠ΄Π΅Ρ ΡΠΎΠ·Π΄Π°Π½ ΡΠΎΠΌ
fsName: cephfs
# (Π½Π΅ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΠΎ) ΠΡΠ» Ceph, Π² ΠΊΠΎΡΠΎΡΠΎΠΌ Π±ΡΠ΄ΡΡ Ρ
ΡΠ°Π½ΠΈΡΡΡΡ Π΄Π°Π½Π½ΡΠ΅ ΡΠΎΠΌΠ°
# pool: cephfs_data
# (Π½Π΅ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΠΎ) Π Π°Π·Π΄Π΅Π»Π΅Π½Π½ΡΠ΅ Π·Π°ΠΏΡΡΡΠΌΠΈ ΠΎΠΏΡΠΈΠΈ ΠΌΠΎΠ½ΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ Π΄Π»Ρ Ceph-fuse
# Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ:
# fuseMountOptions: debug
# (Π½Π΅ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΠΎ) Π Π°Π·Π΄Π΅Π»Π΅Π½Π½ΡΠ΅ Π·Π°ΠΏΡΡΡΠΌΠΈ ΠΎΠΏΡΠΈΠΈ ΠΌΠΎΠ½ΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ CephFS Π΄Π»Ρ ΡΠ΄ΡΠ°
# Π‘ΠΌ. man mount.ceph ΡΡΠΎΠ±Ρ ΡΠ·Π½Π°ΡΡ ΡΠΏΠΈΡΠΎΠΊ ΡΡΠΈΡ
ΠΎΠΏΡΠΈΠΉ. ΠΠ°ΠΏΡΠΈΠΌΠ΅Ρ:
# kernelMountOptions: readdir_max_bytes=1048576,norbytes
# Π‘Π΅ΠΊΡΠ΅ΡΡ Π΄ΠΎΠ»ΠΆΠ½Ρ ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΡ Π΄ΠΎΡΡΡΠΏΡ Π΄Π»Ρ Π°Π΄ΠΌΠΈΠ½Π° ΠΈ/ΠΈΠ»ΠΈ ΡΠ·Π΅ΡΠ° Ceph.
csi.storage.k8s.io/provisioner-secret-name: csi-cephfs-secret
csi.storage.k8s.io/provisioner-secret-namespace: ceph-csi-cephfs
csi.storage.k8s.io/controller-expand-secret-name: csi-cephfs-secret
csi.storage.k8s.io/controller-expand-secret-namespace: ceph-csi-cephfs
csi.storage.k8s.io/node-stage-secret-name: csi-cephfs-secret
csi.storage.k8s.io/node-stage-secret-namespace: ceph-csi-cephfs
# (Π½Π΅ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΠΎ) ΠΡΠ°ΠΉΠ²Π΅Ρ ΠΌΠΎΠΆΠ΅Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Π»ΠΈΠ±ΠΎ ceph-fuse (fuse),
# Π»ΠΈΠ±ΠΎ ceph kernelclient (kernel).
# ΠΡΠ»ΠΈ Π½Π΅ ΡΠΊΠ°Π·Π°Π½ΠΎ, Π±ΡΠ΄Π΅Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡΡΡ ΠΌΠΎΠ½ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΡΠΎΠΌΠΎΠ² ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ,
# ΡΡΠΎ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ΅ΡΡΡ ΠΏΠΎΠΈΡΠΊΠΎΠΌ ceph-fuse ΠΈ mount.ceph
# mounter: kernel
reclaimPolicy: Delete
allowVolumeExpansion: true
mountOptions:
- debug
ΠΠ°ΠΏΠΎΠ»Π½ΠΈΠΌ ΡΡΡ clusterID ΠΈ ΠΏΡΠΈΠΌΠ΅Π½ΠΈΠΌ Π² Kubernetes:
kubectl apply -f storageclass.yaml
ΠΡΠΎΠ²Π΅ΡΠΊΠ°
ΠΠ»Ρ ΠΏΡΠΎΠ²Π΅ΡΠΊΠΈ, ΠΊΠ°ΠΊ ΠΈ Π² ΠΏΡΠΎΡΠ»ΠΎΠΌ ΠΏΡΠΈΠΌΠ΅ΡΠ΅, ΡΠΎΠ·Π΄Π°Π΄ΠΈΠΌ PVC:
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: csi-cephfs-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
storageClassName: csi-cephfs-sc
Π ΠΏΡΠΎΠ²Π΅ΡΠΈΠΌ Π½Π°Π»ΠΈΡΠΈΠ΅ PVC/PV:
kubectl get pvc
kubectl get pv
ΠΡΠ»ΠΈ Ρ ΠΎΡΠ΅ΡΡΡ ΠΏΠΎΡΠΌΠΎΡΡΠ΅ΡΡ Π½Π° ΡΠ°ΠΉΠ»Ρ ΠΈ ΠΊΠ°ΡΠ°Π»ΠΎΠ³ΠΈ Π² CephFS, ΠΌΠΎΠΆΠ½ΠΎ ΠΏΡΠΈΠΌΠΎΠ½ΡΠΈΡΠΎΠ²Π°ΡΡ ΡΡΡ ΡΠ°ΠΉΠ»ΠΎΠ²ΡΡ ΡΠΈΡΡΠ΅ΠΌΡ ΠΊΡΠ΄Π°-Π½ΠΈΠ±ΡΠ΄Ρ. ΠΠ°ΠΏΡΠΈΠΌΠ΅Ρ, ΠΊΠ°ΠΊ ΠΏΠΎΠΊΠ°Π·Π°Π½ΠΎ Π½ΠΈΠΆΠ΅.
Π‘Ρ ΠΎΠ΄ΠΈΠΌ Π½Π° ΠΎΠ΄Π½Ρ ΠΈΠ· Π½ΠΎΠ΄ ΠΊΠ»Π°ΡΡΠ΅ΡΠ° Ceph ΠΈ Π²ΡΠΏΠΎΠ»Π½ΠΈΠΌ ΡΠ°ΠΊΠΈΠ΅ Π΄Π΅ΠΉΡΡΠ²ΠΈΡ:
# Π’ΠΎΡΠΊΠ° ΠΌΠΎΠ½ΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ
mkdir -p /mnt/cephfs
# Π‘ΠΎΠ·Π΄Π°ΡΠΌ ΡΠ°ΠΉΠ» Ρ ΠΊΠ»ΡΡΠΎΠΌ Π°Π΄ΠΌΠΈΠ½ΠΈΡΡΡΠ°ΡΠΎΡΠ°
ceph auth get-key client.admin >/etc/ceph/secret.key
# ΠΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ Π·Π°ΠΏΠΈΡΡ Π² /etc/fstab
# !! ΠΠ·ΠΌΠ΅Π½ΡΠ΅ΠΌ ip Π°Π΄ΡΠ΅Ρ Π½Π° Π°Π΄ΡΠ΅Ρ Π½Π°ΡΠ΅Π³ΠΎ ΡΠ·Π»Π°
echo "172.18.8.6:6789:/ /mnt/cephfs ceph name=admin,secretfile=/etc/ceph/secret.key,noatime,_netdev 0 2" >> /etc/fstab
mount /mnt/cephfs
ΠΠΎΠ½Π΅ΡΠ½ΠΎ ΠΆΠ΅, Π²ΠΎΡ ΡΠ°ΠΊΠΎΠ΅ ΠΌΠΎΠ½ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ FS Π½Π° Π½ΠΎΠ΄Π΅ Ceph ΠΏΠΎΠ΄Ρ
ΠΎΠ΄ΠΈΡ ΠΈΡΠΊΠ»ΡΡΠΈΡΠ΅Π»ΡΠ½ΠΎ Π΄Π»Ρ ΡΠ΅Π»Π΅ΠΉ ΠΎΠ±ΡΡΠ΅Π½ΠΈΡ, ΡΠ΅ΠΌ ΠΌΡ ΠΈ Π·Π°Π½ΠΈΠΌΠ°Π΅ΠΌΡΡ Π½Π° Π½Π°ΡΠΈΡ
ΠΡ ΠΈ Π½Π°ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠΊ Π΄Π°Π²Π°ΠΉΡΠ΅ ΠΏΡΠΎΠ²Π΅ΡΠΈΠΌ, ΠΊΠ°ΠΊ Π² ΡΠ»ΡΡΠ°Π΅ Ρ CephFS ΠΎΠ±ΡΡΠΎΡΡ Π΄Π΅Π»Π° Ρ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ΠΌ ΡΠ°Π·ΠΌΠ΅ΡΠΎΠ² ΡΠΎΠΌΠ°. ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΡΡ Π² Kubernetes ΠΈ ΠΎΡΡΠ΅Π΄Π°ΠΊΡΠΈΡΡΠ΅ΠΌ Π½Π°Ρ ΠΌΠ°Π½ΠΈΡΠ΅ΡΡ Π΄Π»Ρ PVC β ΡΠ²Π΅Π»ΠΈΡΠΈΠΌ ΡΠ°ΠΌ ΡΠ°Π·ΠΌΠ΅Ρ, ΠΊ ΠΏΡΠΈΠΌΠ΅ΡΡ, Π΄ΠΎ 7Gi.
ΠΡΠΈΠΌΠ΅Π½ΠΈΠΌ ΠΎΡΡΠ΅Π΄Π°ΠΊΡΠΈΡΠΎΠ²Π°Π½Π½ΡΠΉ ΡΠ°ΠΉΠ»:
kubectl apply -f pvc.yaml
ΠΠΎΡΠΌΠΎΡΡΠΈΠΌ Π½Π° ΠΏΡΠΈΠΌΠΎΠ½ΡΠΈΡΠΎΠ²Π°Π½Π½ΠΎΠΌ ΠΊΠ°ΡΠ°Π»ΠΎΠ³Π΅, ΠΊΠ°ΠΊ ΠΈΠ·ΠΌΠ΅Π½ΠΈΠ»Π°ΡΡ ΠΊΠ²ΠΎΡΠ°:
getfattr -n ceph.quota.max_bytes <ΠΊΠ°ΡΠ°Π»ΠΎΠ³-Ρ-Π΄Π°Π½Π½ΡΠΌΠΈ>
ΠΠ»Ρ ΡΠ°Π±ΠΎΡΡ ΡΡΠΎΠΉ ΠΊΠΎΠΌΠ°Π½Π΄Ρ, Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ, Π²Π°ΠΌ ΠΏΠΎΡΡΠ΅Π±ΡΠ΅ΡΡΡ ΡΡΡΠ°Π½ΠΎΠ²ΠΈΡΡ Π² ΡΠΈΡΡΠ΅ΠΌΡ ΠΏΠ°ΠΊΠ΅Ρ attr.
ΠΠ»Π°Π·Π° Π±ΠΎΡΡΡΡ, Π° ΡΡΠΊΠΈ Π΄Π΅Π»Π°ΡΡ
Π‘ Π²ΠΈΠ΄Ρ Π²ΡΠ΅ ΡΡΠΈ Π·Π°ΠΊΠ»ΠΈΠ½Π°Π½ΠΈΡ ΠΈ Π΄Π»ΠΈΠ½Π½ΡΠ΅ ΠΌΠ°Π½ΠΈΡΠ΅ΡΡΡ YAML ΠΊΠ°ΠΆΡΡΡΡ ΡΠ»ΠΎΠΆΠ½ΡΠΌΠΈ, Π½ΠΎ Π½Π° ΠΏΡΠ°ΠΊΡΠΈΠΊΠ΅ ΡΡΡΠ΄Π΅Π½ΡΡ Π‘Π»ΡΡΠΌΠ° ΡΠ°Π·Π±ΠΈΡΠ°ΡΡΡΡ Ρ Π½ΠΈΠΌΠΈ Π΄ΠΎΠ²ΠΎΠ»ΡΠ½ΠΎ Π±ΡΡΡΡΠΎ.
Π ΡΡΠΎΠΉ ΡΡΠ°ΡΡΠ΅ ΠΌΡ Π½Π΅ ΡΠ³Π»ΡΠ±Π»ΡΠ»ΠΈΡΡ Π² Π΄Π΅Π±ΡΠΈ β Π΄Π»Ρ ΡΡΠΎΠ³ΠΎ Π΅ΡΡΡ ΠΎΡΠΈΡΠΈΠ°Π»ΡΠ½Π°Ρ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΡ. ΠΡΠ»ΠΈ Π²Π°Ρ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΡΡΡ Π΄Π΅ΡΠ°Π»ΠΈ Π½Π°ΡΡΡΠΎΠΉΠΊΠΈ Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ° Ceph ΡΠΎΠ²ΠΌΠ΅ΡΡΠ½ΠΎ Ρ ΠΊΠ»Π°ΡΡΠ΅ΡΠΎΠΌ Kubernetes, ΠΏΠΎΠΌΠΎΠ³ΡΡ Π²ΠΎΡ ΡΡΠΈ ΡΡΡΠ»ΠΊΠΈ:
ΠΠ° ΠΊΡΡΡΠ΅ Π‘Π»ΡΡΠΌ
Π Π΅ΡΠ»ΠΈ Π²Π°ΠΌ Π±ΠΎΠ»ΡΡΠ΅ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠ½ΠΎ Ρ
ΡΠ°Π½Π΅Π½ΠΈΠ΅ Π΄Π°Π½Π½ΡΡ
, ΡΠΎ Π·Π°ΠΏΠΈΡΡΠ²Π°ΠΉΡΠ΅ΡΡ Π½Π°
ΠΠ²ΡΠΎΡ ΡΡΠ°ΡΡΠΈ: ΠΠ»Π΅ΠΊΡΠ°Π½Π΄Ρ Π¨Π²Π°Π»ΠΎΠ², ΠΏΡΠ°ΠΊΡΠΈΠΊΡΡΡΠΈΠΉ ΠΈΠ½ΠΆΠ΅Π½Π΅Ρ
ΠΡΡΠΎΡΠ½ΠΈΠΊ: habr.com