Привет, Хабр! Сегодня мы попробуем развернуть простой сервис в Kubernetes на примере KaaS в облачной платформе Рег.ру. В качестве самого сервиса будем использовать imgproxy — минималистичный сервис подготовки изображений для web с предельно простым API. 

Этот гайд будет полезен новичкам, которые только начинают работу с Kubernetes. Рассмотрим, как настраивать среду и управлять ей, и освоим принципы работы с контейнерами. Кроме того, развертывание imgproxy в качестве примера поможет научиться обрабатывать изображения с помощью Kubernetes простым и удобным способом.

Навигация по тексту:

Создание кластера Kubernetes
Получение kubeconfig
Проверка kubeconfig
Установка ingress-nginx
Установка cert-manager
Разворачиваем imgproxy
Проверка работоспособности

Создание кластера Kubernetes

Для начала нам нужно создать кластер Kubernetes в личном кабинете облака cloud.reg.ru, например, с тремя воркер-нодами:

Получение kubeconfig

После создания кластера, чтобы к нему подключиться через kubectl, нужно получить для него файл kubeconfig со страницы деталей:

Подсказка: если это ваш единственный кластер Kubernetes, вы можете переместить файл kubeconfig в место по умолчанию: ~/.kube/config и не использовать далее параметр --kubeconfig. Для этого достаточно выполнить в терминале:

→ mv -v <путь к скачанному файлу> ~/.kube/config

В случае, если кластер не единственный и/или управление контекстами в kubectl кажется неудобным, можно использовать разные файлы kubeconfig для разных окружений. Такой способ может быть безопаснее в некоторых случаях: например, если некоторые файлы kubeconfig находятся на внешних зашифрованных носителях.

Проверка kubeconfig

После получения файла kubeconfig нужно проверить его работоспособность. Запросим список нод, неймспейсов, подов:

→ kubectl --kubeconfig ~/Downloads/kubeconfig_4137209 get nodes
NAME                             STATUS   ROLES    AGE   VERSION
k8s4137209-az1-md1-gb86x-6fbhz   Ready    <none>   24m   v1.30.2
k8s4137209-az1-md1-gb86x-cxxwq   Ready    <none>   24m   v1.30.2
k8s4137209-az1-md1-gb86x-pkvvk   Ready    <none>   24m   v1.30.2
→ kubectl --kubeconfig ~/Downloads/kubeconfig_4137209 get namespaces
NAME              STATUS   AGE
default           Active   26m
kube-node-lease   Active   26m
kube-public       Active   26m
kube-system       Active   26m
→ kubectl --kubeconfig ~/Downloads/kubeconfig_4137209 get pods -A
NAMESPACE     NAME                               READY   STATUS    RESTARTS   AGE
kube-system   cilium-99rxc                       1/1     Running   0          24m
kube-system   cilium-envoy-72sxw                 1/1     Running   0          25m
kube-system   cilium-envoy-88bcm                 1/1     Running   0          24m
kube-system   cilium-envoy-xk7t7                 1/1     Running   0          24m
kube-system   cilium-gfb8h                       1/1     Running   0          25m
kube-system   cilium-l4q2c                       1/1     Running   0          24m
kube-system   cilium-operator-5cb45868f8-n55fq   1/1     Running   0          25m
kube-system   coredns-68d4f9776f-l2sbw           1/1     Running   0          25m
kube-system   coredns-68d4f9776f-xjwm7           1/1     Running   0          25m
kube-system   konnectivity-agent-4924v           1/1     Running   0          24m
kube-system   konnectivity-agent-gmgvb           1/1     Running   0          24m
kube-system   konnectivity-agent-v4f2l           1/1     Running   0          24m

Похоже, что все компоненты на месте, ноды есть. kube-apiserver, отдающий нам эту информацию, тоже работает.

Установка ingress-nginx

Начнем с установки ingress controller’а. Он нужен нам для того, чтобы получить трафик внутрь кластера Kubernetes. Использовать ingress’ы — не единственный способ этого достичь, но зачастую самый простой и удобный. В качестве ingress контроллера мы будем использовать ingress-nginx и дополним его cert-manager’ом для обеспечения возможности выписывания TLS-сертификатов от Let’s Encrypt.

Для установки ingress-nginx проще всего использовать helm:

→ helm \
   --kubeconfig ~/Downloads/kubeconfig_4137209 \
   upgrade \
       --install ingress-nginx ingress-nginx/ingress-nginx \
       --namespace ingress-nginx \
       --create-namespace \
	--set controller.kind=DaemonSet

Затем смотрим список подов и сервисов в namespace’е ingress-nginx и убеждаемся, что под с ingress controller’ом запущен, сервис создан и ему выделен внешний IP-адрес:

→ kubectl --kubeconfig ~/Downloads/kubeconfig_4137209 get pods,svc -n ingress-nginx
NAME                                           READY   STATUS    RESTARTS   AGE
pod/ingress-nginx-controller-55dd9c5f4-bv2mc   1/1     Running   0          92s


NAME                                         TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                      AGE
service/ingress-nginx-controller             LoadBalancer   10.105.18.179   89.108.100.213   80:31362/TCP,443:32472/TCP   92s
service/ingress-nginx-controller-admission   ClusterIP      10.102.26.77    <none>           443/TCP                      92s

Установка cert-manager

cert-manager устанавливается по аналогии:

→ helm \
   --kubeconfig ~/Downloads/kubeconfig_4137209 \
   upgrade \
       --install cert-manager jetstack/cert-manager \
       --namespace cert-manager \
       --create-namespace \
       --set crds.enabled=true

Если по какой-то причине эта операция завершится ошибкой, в этом нет ничего страшного. Нужно просто повторить команду.

Единственное отличие: нам нужно будет создать ClusterIssuer для использования Let’s Encrypt, который мы возьмем прямо из документации cert-manager:

---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
 name: letsencrypt-prod
spec:
 acme:
   # The ACME server URL
   server: https://acme-v02.api.letsencrypt.org/directory
   # Email address used for ACME registration
   email: user@example.com
   # Name of a secret used to store the ACME account private key
   privateKeySecretRef:
     name: letsencrypt-prod
   # Enable the HTTP-01 challenge provider
   solvers:
     - http01:
         ingress:
           ingressClassName: nginx

Сохраним в файл и выполним kubectl apply на него:

→ cat <<EOF > clusterissuer.yaml
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
 name: letsencrypt-prod
spec:
 acme:
   # The ACME server URL
   server: https://acme-v02.api.letsencrypt.org/directory
   # Email address used for ACME registration
   email: user@example.com
   # Name of a secret used to store the ACME account private key
   privateKeySecretRef:
     name: letsencrypt-prod
   # Enable the HTTP-01 challenge provider
   solvers:
     - http01:
         ingress:
           ingressClassName: nginx
EOF


→ kubectl --kubeconfig ~/Downloads/kubeconfig_4137209 apply -f clusterissuer.yaml

Теперь нам нужно определиться с доменным именем, на котором будет работать наше тестовое приложение imgproxy. Я выберу поддомен imgproxy к одному из своих доменов и добавлю для него A-запись в DNS со значением внешнего IP сервиса - 89.108.100.213 в примере выше (у вас будет свой IP-адрес, ну и поддомен вы также можете выбрать свой).

Разворачиваем imgproxy

На этом инфраструктурная часть фактически закончена, и мы можем развернуть imgproxy. Он уже собран и поставляется в виде образа контейнера, для него даже есть готовый Helm Chart. Сам imgproxy в своей конфигурации очень прост, и в данном случае хочется показать “подкапотное пространство”, потому егомы развернем вручную, т.е. с использованием описаний самого Kubernetes. Для этого нам потребуется создать три ресурса: Deployment — с самим с приложением, Service — для организации балансировки между несколькими инстансами imgproxy и Ingress — для обеспечения доступа из сети Интернет. Бонусом, т.к. мы настроили cert-manager, мы получим TLS для нашего imgproxy.

Создаем deployment.yaml:

→ cat <<EOF > deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
 name: imgproxy
 labels:
   app.kubernetes.io/name: imgproxy
   app.kubernetes.io/app: imgproxy
spec:
 replicas: 3
 strategy:
   type: RollingUpdate
 revisionHistoryLimit: 3
 selector:
   matchLabels:
     app.kubernetes.io/name: imgproxy
     app.kubernetes.io/app: imgproxy
 template:
   metadata:
     labels:
       app.kubernetes.io/name: imgproxy
       app.kubernetes.io/app: imgproxy
   spec:
     affinity:
       podAntiAffinity:
         requiredDuringSchedulingIgnoredDuringExecution:
         - labelSelector:
             matchExpressions:
             - key: app.kubernetes.io/app
               operator: In
               values:
               - imgproxy
           topologyKey: "kubernetes.io/hostname"
     terminationGracePeriodSeconds: 30
     automountServiceAccountToken: false
     containers:
       - name: imgproxy
         image: darthsim/imgproxy:latest
         imagePullPolicy: IfNotPresent
         env:
             # the maximum duration (in seconds) for processing the response.
             # Default: 10
           - name: IMGPROXY_WRITE_TIMEOUT
             value: '30'
             # the maximum duration (in seconds) to wait for the next request
             # before closing the connection. When set to 0, keep-alive is
             # disabled. Default: 10
           - name: IMGPROXY_KEEP_ALIVE_TIMEOUT
             value: '30'
             # the maximum duration (in seconds) to wait for the next request
             # before closing the HTTP client connection. The HTTP client is
             # used to download source images. When set to 0, keep-alive is
             # disabled. Default: 90
           - name: IMGPROXY_CLIENT_KEEP_ALIVE_TIMEOUT
             value: '40'
             # the maximum duration (in seconds) for downloading the source
             # image. Default: 5
           - name: IMGPROXY_DOWNLOAD_TIMEOUT
             value: '30'
             # when set to true, enables using the ETag HTTP header for HTTP
             # cache control. Default: false
           - name: IMGPROXY_USE_ETAG
             value: 'true'
             # when set to true, enables using the Last-Modified HTTP header
             # for HTTP cache control. Default: false
           - name: IMGPROXY_USE_LAST_MODIFIED
             value: 'true'
             # when set to true, imgproxy will add debug headers to the response.
             # Default: false. The following headers will be added:
             #
             # X-Origin-Content-Length: the size of the source image
             # X-Origin-Width: the width of the source image
             # X-Origin-Height: the height of the source image
             # X-Result-Width: the width of the resultant image
             # X-Result-Height: the height of the resultant image
           - name: IMGPROXY_ENABLE_DEBUG_HEADERS
             value: 'true'
             # the maximum resolution of the source image, in megapixels.
             # Images with larger actual size will be rejected. Default: 50
           - name: IMGPROXY_MAX_SRC_RESOLUTION
             value: '70'
             # the maximum size of the source image, in bytes. Images with
             # larger file size will be rejected. When set to 0, file size
             # check is disabled. Default: 0
           - name: IMGPROXY_MAX_SRC_FILE_SIZE
             value: '104857600'
             # the default quality of the resultant image, percentage.
             # Default: 80
           - name: IMGPROXY_QUALITY
             value: '90'
             # default quality of the resulting image per format, separated
             # by commas. Example: jpeg=70,avif=40,webp=60. When a value for
             # the resulting format is not set, the IMGPROXY_QUALITY value is
             # used. Default: avif=65
           - name: IMGPROXY_FORMAT_QUALITY
             value: 'jpeg=90'
         ports:
           - name: http
             containerPort: 8080
             protocol: TCP
         resources:
           requests:
             cpu: 10m
             memory: 32Mi
           limits:
             memory: 1Gi
         securityContext:
           allowPrivilegeEscalation: false
           readOnlyRootFilesystem: true
EOF

service.yaml:

→ cat <<EOF > service.yaml
---
apiVersion: v1
kind: Service
metadata:
 name: imgproxy
 labels:
   app.kubernetes.io/name: imgproxy
   app.kubernetes.io/app: imgproxy
spec:
 ports:
 - name: http
   port: 8080
   protocol: TCP
   targetPort: 8080
 selector:
   app.kubernetes.io/name: imgproxy
   app.kubernetes.io/app: imgproxy
 type: ClusterIP
EOF

В ingress.yaml нужно поменять название домена на свой собственный, для которого была добавлена A-запись:

→ cat <<EOF > ingress.yaml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
 annotations:
   ingress.kubernetes.io/ssl-redirect: "true"
   nginx.ingress.kubernetes.io/ssl-redirect: "true"
   kubernetes.io/ingress.class: nginx
   kubernetes.io/tls-acme: "true"
   cert-manager.io/cluster-issuer: letsencrypt-prod
   nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
   nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
   nginx.ingress.kubernetes.io/client-body-buffer-size: 256m
   nginx.ingress.kubernetes.io/proxy-body-size: 256m
 labels:
   app.kubernetes.io/name: imgproxy
   app.kubernetes.io/app: imgproxy
 name: imgproxy
spec:
 ingressClassName: nginx
 rules:
 - host: imgproxy.my-domain.ru
   http:
     paths:
     - backend:
         service:
           name: imgproxy
           port:
             number: 8080
       path: /
       pathType: Prefix
 tls:
 - hosts:
   - imgproxy.my-domain.ru
   secretName: imgproxy.my-domain.ru
EOF

Примечание: в примере выше используется доменное имя imgproxy.my-domain.ru, его нужно заменить на свое собственное.

После создания файлов применяем изменения:

kubectl apply -f deployment.yaml -f service.yaml -f ingress.yaml

Проверка работоспособности

Открываем браузер и идем по адресу https://<наш домен>/unsafe/resize:fit:300:300/plain/https:%2F%2Fmars.nasa.gov%2Fsystem%2Fdownloadable_items%2F40368_PIA22228.jpg

В результате мы видим изображение с размером, вписанным в квадрат 300x300 пикселей, т.е. все работает.

Мы пошагово разобрали, как развернуть сервис imgproxy в Kubernetes с помощью KaaS в облаке Рег.ру. Теперь, когда сервис работает, можно смело настраивать его, масштабировать и оптимизировать для своих задач. Если вы хотите углубиться в тему Kubernetes, прочитайте наши предыдущие статьи. Ранее мы писали про архитектуру KaaS и рассказывали о том, как мы его запускали в облаке Рег.ру.

Комментарии (1)


  1. Razoon
    07.11.2024 11:16

    Не сочтите за грубость, но название вашей компании так и подмывает прочесть как руинити) Кажется что у вас есть ошибка в корпоративном нейминге)))