Thursday, November 21, 2024

Triển khai Redis trên Kubernetes với Helm Chart

-

1. Tổng quan.

Redis là hệ thống lưu trữ dữ liệu key-value, Kubernetes là một nền tảng quản lý và triển khai ứng dụng trong các containers, và Helm là một công cụ giúp quản lý và cài đặt các ứng dụng trên Kubernetes bằng cách sử dụng các gói gọi là charts.

  • Kubernetes: Kubernetes là một nền tảng mã nguồn mở dùng để quản lý và triển khai các ứng dụng được đóng gói trong các containers. Nó giúp tự động hóa việc triển khai, mở rộng và quản lý các ứng dụng. Kubernetes cung cấp các khả năng như việc quản lý tài nguyên, cân bằng tải, phục hồi tự động, và khả năng mở rộng linh hoạt.
  • Helm: Helm là một công cụ quản lý gói và cài đặt ứng dụng trên Kubernetes. Nó cho phép bạn đóng gói ứng dụng vào các gói được gọi là “charts” để dễ dàng chia sẻ và cài đặt. Mỗi chart bao gồm các file mô tả tài nguyên Kubernetes (ví dụ: deployments, services, configmaps) và các giá trị cấu hình tùy chỉnh (values) cho các ứng dụng. Helm giúp đơn giản hóa việc quản lý và cài đặt ứng dụng trên Kubernetes.
  • Redis: Redis là một hệ thống lưu trữ dữ liệu cơ bản dựa trên cấu trúc dữ liệu key-value. Nó thường được sử dụng để lưu trữ dữ liệu tạm thời, cache, thông tin phiên, và nhiều ứng dụng khác. Redis hỗ trợ nhiều loại dữ liệu như strings, hashes, lists, sets, sorted sets và nhiều chức năng khác. Nó nổi tiếng với hiệu suất cao và khả năng mở rộng.

2. Redis Cluster là gì?

Redis Cluster: là một tính năng của Redis cho phép bạn triển khai và quản lý một cụm (cluster) các máy chủ Redis để cải thiện khả năng sẵn sàng, hiệu suất và khả năng mở rộng của hệ thống. Redis Cluster giúp bạn chia dữ liệu thành nhiều mảng, gọi là “slots,” và phân phối chúng trên các node khác nhau trong cụm.

Một số điểm quan trọng về Redis Cluster:

  • Phân chia dữ liệu: Dữ liệu được chia thành nhiều slots (khoảng 16384 slots) và phân phối đều đặn trên các node trong cụm.
  • Sẵn sàng và sao lưu: Redis Cluster cung cấp khả năng sao lưu dữ liệu trên nhiều node, giúp đảm bảo sẵn sàng và khả năng khôi phục dữ liệu khi có sự cố.
  • Hiệu suất: Khi tăng số lượng node, Redis Cluster có thể cân bằng tải tự động để tối ưu hiệu suất của hệ thống.
  • Tích hợp tối ưu hóa: Redis Cluster cung cấp tích hợp với các tính năng như client-side sharding (phân chia client) và automatic partitioning (phân chia tự động), giúp người dùng tương tác với cụm một cách dễ dàng.
  • Khả năng mở rộng: Bạn có thể mở rộng Redis Cluster bằng cách thêm node mới vào cụm.

Lưu ý rằng việc triển khai và quản lý một Redis Cluster đòi hỏi sự hiểu biết về cách hoạt động của Redis và cụm cũng như việc cấu hình một cách chính xác để đảm bảo tính sẵn sàng và hiệu suất tốt.

3. Khái niệm về Redis Master, Redis Replicas và Redis Headless.

Redis Master là node chính quản lý việc ghi dữ liệu, Redis Replicas là các bản sao dữ liệu từ Redis Master để đảm bảo sẵn sàng và tăng hiệu suất đọc, và Redis Headless Service là một dịch vụ trong Kubernetes để cung cấp tên miền DNS cho các Pod Redis để truy cập trực tiếp.

  • Redis Master: Redis Master là node chính trong một cụm Redis Cluster hoặc một triển khai Redis có sao lưu. Nó chịu trách nhiệm cho việc ghi dữ liệu và quản lý dữ liệu. Các node Redis Replicas được sao lưu từ Redis Master để đảm bảo sẵn sàng dữ liệu và tăng khả năng đọc.
  • Redis Replicas: Redis Replicas (hoặc Redis Slave) là các bản sao dữ liệu được tạo ra từ Redis Master. Chúng không có quyền ghi dữ liệu mà chỉ đọc. Việc sử dụng Redis Replicas giúp tăng hiệu suất đọc và cung cấp khả năng sẵn sàng. Nếu Redis Master gặp sự cố, bạn có thể chuyển đổi các Replica thành Master để tiếp tục hoạt động.
  • Redis Headless Service: Trong triển khai Redis trên Kubernetes, một Redis Headless Service thường được tạo để tạo ra một tên miền DNS cho mỗi Pod Redis. Điều này cho phép bạn truy cập từng Pod Redis thông qua tên DNS của nó. Headless Service thường được sử dụng trong các trường hợp cần thực hiện truy vấn đến các Pod cụ thể mà không cần cân bằng tải, ví dụ như trong Redis Cluster hoặc khi bạn muốn thao tác với các node cụ thể trong một Redis replica set.

4. Thực hành triển khai Redis Cluster sử dụng Heml.

Bước 1 – Thêm Bitnami vào Helm Repo.

Bạn có thể sử dụng cú pháp dưới để thêm 1 Helm repo.

helm repo add [repo-name] [repo-address]

Ví dụ dưới đây mình sẽ thêm repo https://charts.bitnami.com/bitnami và đặt tên cho nó là bitnami.

$ helm repo add bitnami https://charts.bitnami.com/bitnami
"bitnami" has been added to your repositories

Sau khi thêm repo xong bạn hãy cập nhật lại repo này.

$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "longhorn" chart repository
...Successfully got an update from the "nginx-stable" chart repository
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈

Sử dụng lệnh helm repo list sẽ giúp bạn xem danh sách các repo có trong hệ thống.

$ helm repo list
NAME            URL                               
nginx-stable    https://helm.nginx.com/stable     
longhorn        https://charts.longhorn.io        
bitnami         https://charts.bitnami.com/bitnami

Bước 2 – Triển khai Redis Cluster.

Mặc định các bạn có thể sử dụng lệnh dưới để cài đặt nhanh 1 Redis Cluster nhưng tôi khuyên chúng ta không nên làm vậy vì chúng ta nên thay đổi một số giá trị để nó phù hợp với môi trường của bạn. Nếu bạn muốn trải nghiệm hãy bỏ qua bước này và thực hiện các bước tiếp theo.

helm install redis-test bitnami/redis

Hãy tải về Helm chart bitnami/redis từ kho lưu trữ, bạn sẽ nhận được file nén *.tgz.

helm fetch bitnami/redis

Bạn sử dụng lệnh dưới để verify lại kết quả tải về thành công.

$ ls . | grep *.tgz

Để giải nén một file .tgz (tarball) của Helm chart, bạn cần sử dụng câu lệnh tar để giải nén file này. Dưới đây là cách giải nén Helm chart Redis từ file redis-17.15.4.tgz:

tar -xzvf redis-17.15.4.tgz

Di chuyển vào thư mục đã giải nén và kiểm tra sự tồn tại của file valule.yaml.

$ cd redis/
$ ls . | grep values.yaml
values.yaml

Hãy dùng công cụ đọc file để đọc file value.yaml và chỉnh sử 1 số thông tin mà bạn mong muốn, ví dụ của mình là thông tin storageClass vì mình đã có 1 Longhorn Storage và cài đặt dung lượng phân vùng cho master và replica.

global:
  storageClass: "longhorn"

master:
    storageClass: "longhorn"
    size: 50Gi

replica:
    storageClass: "longhorn"
    size: 100Gi

Dùng lệnh dưới đây để list ra các storageClass đang có trong hệ thống của bạn.

$ kubectl get storageclass
NAME                 PROVISIONER          RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
longhorn (default)   driver.longhorn.io   Delete          Immediate           true                   92d

Mình tạo 1 file yaml để khởi tạo 1 namespace tách biệt với các ứng dụng khác.

cat > namespace.yaml << 'OEF'
apiVersion: v1
kind: Namespace
metadata:
  name: redis
OEF

Triển khai file yaml đã tạo.

$ kubectl apply -f namespace.yaml
namespace/redis created

Kết quả mình đã có 1 namespace tên là redis.

$ kubectl get ns redis
NAME    STATUS   AGE
redis   Active   10s

Triển khai Redis bằng Helm như sau:

helm install redis-test \
  --set persistence.storageClass=longhorn \
  --set redis.replica.persistence.storageClass=longhorn \
  --set volumePermissions.enabled=true \
  --namespace=redis \
  --values /home/redis/redis/values.yaml \
  bitnami/redis

Trong đó, --set persistence.storageClass=longhorn--set redis.replica.persistence.storageClass=longhorn đảm bảo rằng bạn đang sử dụng StorageClass “longhorn” cho cả PVC chính và PVC của replica.

  • Đảm bảo rằng bạn đã chỉ định tên của namespace (trong ví dụ này, namespace là “redis”) bằng cách sử dụng tùy chọn --namespace.
  • --values /home/redis/redis/values.yaml cho phép bạn sử dụng file values.yaml tùy chỉnh từ thư mục /home/redis/redis.

Nhớ rằng bạn cần đảm bảo file values.yaml tùy chỉnh chứa các khóa cấu hình mà Helm chart yêu cầu và tương thích với cấu trúc của Helm chart.

Đây là ví dụ đầu ra của nó khi triển khai thành công.

$ helm install redis-test \
>   --set persistence.storageClass=longhorn \
>   --set redis.replica.persistence.storageClass=longhorn \
>   --set volumePermissions.enabled=true \
>   --namespace=redis \
>   --values /home/redis/redis/values.yaml \
>   bitnami/redis
NAME: redis-test
LAST DEPLOYED: Thu Aug 17 09:55:05 2023
NAMESPACE: redis
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: redis
CHART VERSION: 17.15.4
APP VERSION: 7.2.0

** Please be patient while the chart is being deployed **

Redis® can be accessed on the following DNS names from within your cluster:

    redis-test-master.redis.svc.cluster.local for read/write operations (port 6379)
    redis-test-replicas.redis.svc.cluster.local for read-only operations (port 6379)



To get your password run:

    export REDIS_PASSWORD=$(kubectl get secret --namespace redis redis-test -o jsonpath="{.data.redis-password}" | base64 -d)

To connect to your Redis® server:

1. Run a Redis® pod that you can use as a client:

   kubectl run --namespace redis redis-client --restart='Never'  --env REDIS_PASSWORD=$REDIS_PASSWORD  --image docker.io/bitnami/redis:7.2.0-debian-11-r0 --command -- sleep infinity

   Use the following command to attach to the pod:

   kubectl exec --tty -i redis-client \
   --namespace redis -- bash

2. Connect using the Redis® CLI:
   REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h redis-test-master
   REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h redis-test-replicas

To connect to your database from outside the cluster execute the following commands:

    kubectl port-forward --namespace redis svc/redis-test-master 6379:6379 &
    REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h 127.0.0.1 -p 6379

Bước 3 – Xác nhận các dịch vụ triển khai đã chạy.

Bạn có thể verify lại PersistentVolume (PV) và PersistentVolumeClaim (PVC).

$ kubectl get pv,pvc -n redis
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                    STORAGECLASS   REASON   AGE
persistentvolume/pvc-1a48197a-16bc-48de-a8b5-733754331550   100Gi      RWO            Delete           Bound    devops-tools-master/jenkins-pv-claim     longhorn                44d
persistentvolume/pvc-3121a890-df7a-4928-b41f-17024fb7df29   10Gi       RWO            Delete           Bound    wiki-hoanghd/website-mysql-pvc           longhorn                44d
persistentvolume/pvc-3bda4320-e917-462f-85d9-f144edfd5621   100Gi      RWO            Delete           Bound    redis/redis-data-redis-test-replicas-0   longhorn                19s
persistentvolume/pvc-3fc21d5b-b3e8-49eb-aa82-dc40c1ccbfef   100Gi      RWO            Delete           Bound    devops-tools-slave/jenkins-pv-claim      longhorn                44d
persistentvolume/pvc-6001eab0-493e-4182-be42-6afc5891fb8d   10Gi       RWO            Delete           Bound    matbao/website-mysql-pvc                 longhorn                44d
persistentvolume/pvc-b41d891f-38a6-455d-a7f3-b649b5fa367c   50Gi       RWO            Delete           Bound    matbao/website-wp-pvc                    longhorn                44d
persistentvolume/pvc-b895c850-092c-40f0-a307-e12fbcfe6e23   50Gi       RWO            Delete           Bound    wiki-hoanghd/website-wp-pvc              longhorn                44d
persistentvolume/pvc-cbe5eda4-13f5-4028-9067-dd1a130252cc   50Gi       RWO            Delete           Bound    redis/redis-data-redis-test-master-0     longhorn                19s

NAME                                                     STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/redis-data-redis-test-master-0     Bound    pvc-cbe5eda4-13f5-4028-9067-dd1a130252cc   50Gi       RWO            longhorn       21s
persistentvolumeclaim/redis-data-redis-test-replicas-0   Bound    pvc-3bda4320-e917-462f-85d9-f144edfd5621   100Gi      RWO            longhorn       21s

Hoặc có thể verify trên Longhorn storage.

Bước 4 – Tạo môi trường test.

Lấy thông tin về secret và giải mã secret base64

export REDIS_PASSWORD=$(kubectl get secret --namespace redis redis-test -o jsonpath="{.data.redis-password}" | base64 --decode)

Dòng lệnh này thực hiện các bước sau:

  • kubectl get secret --namespace default redis-test -o jsonpath="{.data.redis-password}": Lấy thông tin về secret có tên redis-test trong namespace redis. Trong đó, redis-password là một trường dữ liệu trong secret chứa mật khẩu của Redis.
  • base64 --decode: Giải mã dữ liệu base64 của trường redis-password, để lấy ra mật khẩu thật sự.
  • export REDIS_PASSWORD=$(...): Gán mật khẩu Redis đã giải mã vào biến môi trường REDIS_PASSWORD.

Kết quả của dòng lệnh này là bạn đã tạo một biến môi trường REDIS_PASSWORD chứa mật khẩu của Redis để có thể sử dụng trong các lệnh hoặc mã nguồn khác mà bạn cần truy cập vào Redis.

Lưu ý rằng lệnh này đang giả định rằng bạn đang sử dụng namespace redis. Nếu Redis được triển khai trong một namespace khác, bạn cần thay đổi --namespace default thành --namespace <tên-namespace> tương ứng.

Kết quả.

$ echo $REDIS_PASSWORD
n3V8kJjllf

Tạo một Pod có tên redis-client trong namespace redis.

kubectl run --namespace redis redis-client --restart='Never'  --env REDIS_PASSWORD=$REDIS_PASSWORD  --image docker.io/bitnami/redis:6.2.5-debian-10-r63 --command -- sleep infinity

Lệnh trên sẽ thực hiện các bước sau:

  • kubectl run --namespace redis redis-client --restart='Never': Tạo một Pod có tên redis-client trong namespace redis và chỉ định rằng nó sẽ không khởi động lại khi kết thúc.
  • --env REDIS_PASSWORD=$REDIS_PASSWORD: Đặt biến môi trường REDIS_PASSWORD trong Pod bằng giá trị của biến môi trường REDIS_PASSWORD mà bạn đã xuất trước đó.
  • --image docker.io/bitnami/redis:6.2.5-debian-10-r63: Sử dụng hình ảnh Redis từ Docker Hub.
  • --command -- sleep infinity: Chạy lệnh sleep infinity bên trong Pod, dẫn đến việc Pod sẽ chạy vô thời hạn (không bao giờ dừng) vì lệnh sleep infinity đợi mãi mã không bao giờ kết thúc.

Nhưng dòng lệnh này không có mục tiêu rõ ràng. Thường thì người ta sử dụng một Pod như vậy để thực hiện các tác vụ kiểm tra, gỡ lỗi hoặc thao tác tạm thời với hệ thống. Trong trường hợp này, Pod redis-client sẽ không làm gì ngoài việc “ngủ” vô thời hạn.

Nếu bạn cần thực hiện một tác vụ cụ thể bên trong Pod, hãy thay sleep infinity bằng lệnh cụ thể bạn muốn chạy.

Tạo một phiên làm việc tương tác (interactive session) bên trong container của Pod redis-client, trong đó bạn có thể sử dụng dòng lệnh bash để thực hiện các lệnh hoặc kiểm tra trong môi trường của container.

kubectl exec --tty -i redis-client --namespace redis -- bash

Lệnh trên sẽ thực hiện các bước sau:

  • kubectl exec --tty -i redis-client --namespace redis -- bash: Thực hiện lệnh exec để thực thi lệnh bên trong container của Pod redis-client trong namespace redis.
  • --tty -i: Tạo một kết nối tương tác và allocate một terminal (TTY) cho việc truy cập vào container.
  • --namespace redis: Xác định namespace trong đó Pod redis-client được triển khai.
  • -- bash: Chạy một phiên bản của dòng lệnh bash bên trong container. Điều này cho phép bạn truy cập vào môi trường dòng lệnh của container để thực hiện các tác vụ bổ sung bên trong.

Nếu bạn vào được container thành công, bạn sẽ có kết quả như dưới.

$ kubectl exec --tty -i pod/redis-client --namespace redis -- bash
I have no name!@redis-client:/$

redis-cli -h redis-test-master -a $REDIS_PASSWORD sử dụng redis-cli để kết nối đến máy chủ Redis có tên là redis-test-master. Điều này đặc biệt hữu ích trong môi trường Redis có cấu hình replica, nơi bạn muốn tương tác trực tiếp với master node để thực hiện các thao tác chỉnh sửa dữ liệu (ví dụ: ghi dữ liệu mới).

$ redis-cli -h redis-test-master -a $REDIS_PASSWORD
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.

redis-cli -h redis-test-replicas -a $REDIS_PASSWORD sử dụng redis-cli để kết nối đến máy chủ Redis có tên là redis-test-replicas. Điều này thường được sử dụng để truy vấn các replica nodes, nơi dữ liệu chỉ được đọc và không nên thay đổi (ví dụ: truy vấn dữ liệu).

$ redis-cli -h redis-test-replicas -a $REDIS_PASSWORD
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.

Nhớ rằng cách bạn tương tác với master và replica nodes phụ thuộc vào mục đích của bạn. Master node thường được sử dụng để thực hiện các thao tác ghi dữ liệu, trong khi replica nodes thường được sử dụng để đọc dữ liệu và tăng tính sẵn sàng và hiệu suất của hệ thống.

Sử dụng lệnh PING để kiểm tra Redis đã hoạt động.

$ redis-test-replicas:6379> PING
PONG

Trong Redis, lệnh PING là một trong những lệnh cơ bản nhất và được sử dụng để kiểm tra xem máy chủ Redis có hoạt động bình thường hay không. Khi bạn chạy lệnh PING trong redis-cli, Redis server sẽ trả về PONG nếu máy chủ đang hoạt động đúng cách.

Trong trường hợp bạn đưa ra, khi bạn thực hiện redis-cli để kết nối đến máy chủ Redis có tên redis-test-replicas và chạy lệnh PING, kết quả trả về là PONG. Điều này cho thấy rằng máy chủ Redis redis-test-replicas đang hoạt động và phản hồi bình thường.

Lệnh PING và phản hồi PONG thường được sử dụng trong các tác vụ kiểm tra hoặc đồng bộ hóa đơn giản để đảm bảo rằng máy chủ Redis vẫn hoạt động và có thể gửi và nhận dữ liệu.

Bước 5 – Tạo Ingress.

Ingress là một tài nguyên trong Kubernetes cho phép bạn quản lý các quy tắc định tuyến và các luồng lưu lượng truy cập đến các dịch vụ trong cluster.

Vì các service mình để ở chế độ ClusterIP nên mình sẽ dùng Ingress để cho phép các kết nối từ bên ngoài vào trong Redis.

Dùng lệnh kubectl get svc -n redis để list các service của Redis.

$ kubectl get svc -n redis
NAME                  TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
redis-test-headless   ClusterIP   None             <none>        6379/TCP   172m
redis-test-master     ClusterIP   10.107.225.158   <none>        6379/TCP   172m
redis-test-replicas   ClusterIP   10.104.33.176    <none>        6379/TCP   172m

Giả sử mình tạo Ingress cho redis-test-master. Hãy chạy lệnh dưới để vào lấy thông tin service name và port của kubectl get svc -n redis.

kubectl edit svc/redis-test-master -n redis

Hai thông tin trên nằm ở vùng khoanh đỏ như hình dưới.

Tạo 1 file manifest với nội dung dưới.

cat > master-redis-ingress.yml << 'OEF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: redis-ingress
  namespace: redis
spec:
  ingressClassName: nginx
  rules:
  - host: master-redis.hoanghd.com
    http:
      paths:
      - backend:
          service:
            name: tcp-redis
            port:
              number: 6379
        path: /
        pathType: Prefix
OEF

Dưới đây là giải thích cho các phần quan trọng trong tài nguyên Ingress trên:

  • apiVersion: networking.k8s.io/v1: Đây là phiên bản API mà tài nguyên Ingress đang sử dụng.
  • kind: Ingress: Đây là loại tài nguyên, trong trường hợp này là Ingress.
  • metadata: Đây là thông tin về tài nguyên, bao gồm tên và namespace của Ingress.
  • spec: Phần này chứa cấu hình cho Ingress.
    • ingressClassName: nginx: Đây là tên của Ingress Controller mà bạn muốn sử dụng để xử lý các quy tắc Ingress. Trong trường hợp này, nó được gọi là “nginx”.
    • rules: Đây là nơi bạn xác định các quy tắc định tuyến.
      • host: master-redis.hoanghd.com: Đây là tên miền của máy chủ mà Ingress sẽ xử lý lưu lượng truy cập đến.
        • http: Đây xác định rằng bạn đang xử lý lưu lượng truy cập HTTP.
          • paths: Đây là một danh sách các đường dẫn mà bạn muốn Ingress định tuyến đến.
            • backend: Đây là dịch vụ mục tiêu mà lưu lượng truy cập sẽ được định tuyến đến.
              • service: Đây là tên của dịch vụ mục tiêu.
                • name: tcp-redis: Đây là tên của dịch vụ mục tiêu là “tcp-redis”.
              • port: Đây là cổng của dịch vụ mục tiêu.
                • number: 6379: Đây là số cổng (port) của dịch vụ mục tiêu là 6379.
            • path: /: Đây là đường dẫn đích mà lưu lượng truy cập sẽ được định tuyến đến. Trong trường hợp này, nó là root (“/”).
            • pathType: Prefix: Đây chỉ định kiểu đường dẫn (path type) cho việc định tuyến. Trong trường hợp này, nó là “Prefix”, nghĩa là các URL bắt đầu bằng đường dẫn cụ thể sẽ được định tuyến đến dịch vụ.

Triển khai nó.

$ kubectl apply -f redis-ingress.yml 
ingress.networking.k8s.io/jenkins-ingress created

Đây là Ingress sau khi tạo xong.

$ kubectl get ingress -n redis
NAME              CLASS   HOSTS                      ADDRESS          PORTS   AGE
jenkins-ingress   nginx   master-redis.hoanghd.com   192.168.13.221   80      13s

Kết quả.

$ telnet master-redis.hoanghd.com 80
Trying 192.168.13.221...
Connected to master-redis.hoanghd.com.
Escape character is '^]'.

Tương tự bạn có thể tạo Ingress cho redis-test-replicas theo cách trên.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

4,956FansLike
256FollowersFollow
223SubscribersSubscribe
spot_img

Related Stories