Це багатосторінковий друкований вигляд цього розділу. Натисність щоб друкувати.

Повернутися до звичайного перегляду сторінки.

Посібники

Цей розділ документації Kubernetes містить посібники. Посібник показує, як досягти мети, яка більша за одне завдання. Зазвичай посібник має кілька розділів, кожен з яких має послідовність кроків. Перед тим, як пройти кожен посібник, ви можете додати сторінку глосарія до закладок для подальших посилань.

Основи

Налаштування

Створення Podʼів

Stateless-застосунки

Stateful-застосунки

Сервіси (Services)

Безпека

Керування кластером

Що далі

Якщо ви хочете написати посібник, див. Типи сторінок для отримання інформації про типи сторінок посібника.

1 - Привіт Minikube

Цей посібник покаже вам, як запустити простий кластер Kubernetes використовуючи minikube. Посібник надає образ контейнера, який використовує NGINX для відклику на всі запити.

Цілі

  • Розгортання тестового застосунку в minikube.
  • Запуск застосунку.
  • Перегляд логів застосунку.

Перш ніж ви розпочнете

Цей застосунок передбачає, що у вас вже є встановлений minikube. Дивіться Крок 1 в minikube start для інструкцій щодо встановлення.

Вам також потрібно встановити kubectl. Дивіться Встановлення інструментів для інструкцій щодо встановлення.

Створення кластера minikube

minikube start

Відкрийте інформаційну панель

Відкрийте інформаційну панель Kubernetes. Це можна зробити двома різними способами:

Відкрийте новий термінал та виконайте команду:

# Запустіть новий термінал та залиште його працювати.
minikube dashboard

Тепер поверніться до термінала, де ви запустили minikube start.

Якщо ви не хочете, щоб minikube відкривав вебоглядач для вас, запустіть підкоманду dashboard з прапорцем --url. minikube виводить URL-адресу, яку ви можете відкрити у вибраному вами вебоглядачі.

Відкрийте новий термінал та виконайте команду:

# Запустіть новий термінал та залиште його працювати.
minikube dashboard --url

Тепер поверніться до термінала, де ви запустили minikube start.

Створення Deployment

Pod в Kubernetes — це група з одного або більше контейнерів, які повʼязуються один з одним для керування та використання мережевих ресурсів. Pod в цьому посібнику містить тільки один контейнер. Kubernetes Deployment перевіряє життєздатність вашого Podʼа та, якщо він виходить з ладу, перезапускає його. Deployment є рекомендованим способом створення та масштабування Podʼів.

  1. Скористайтесь командою kubectl create для створення Deployment, що буде керувати Podʼом. Pod виконує контейнер, який міститься в образі Docker.

    # Запустіть тестовий образ контейнера, який містить вебсервер
    kubectl create deployment hello-node --image=registry.k8s.io/e2e-test-images/agnhost:2.39 -- /agnhost netexec --http-port=8080
    
  2. Перевірте, чи створено Deployment.

    kubectl get deployments
    

    Ви маєте отримати вивід, подібний до такого:

    NAME         READY   UP-TO-DATE   AVAILABLE   AGE
    hello-node   1/1     1            1           1m
    

    (Зачекайте деякий час, поки Pod стане доступним. Якщо ви бачите "0/1", спробуйте ще раз через кілька секунд.)

  3. Перевірте, чи створено Pod.

    kubectl get pods
    

    Ви маєте отримати вивід, подібний до такого:

    NAME                          READY   STATUS    RESTARTS   AGE
    hello-node--5f76cf6ccf-br9b5  1/1     Running   0          1m
    
  4. Перегляд подій кластера:

    kubectl get events
    
  5. Перегляд конфігурації kubectl:

    kubectl config view
    
  6. Перегляд логів застосунку з контейнера в Podʼі (замініть назву Podʼа на ту, яку ви отримали з kubectl get pods).

    kubectl logs hello-node-5f76cf6ccf-br9b5
    

    Вивід має бути подібним до такого:

    I0911 09:19:26.677397       1 log.go:195] Started HTTP server on port 8080
    I0911 09:19:26.677586       1 log.go:195] Started UDP server on port  8081
    

Створення Service

Стандартно, Pod доступний лише за його внутрішньою IP-адресою в межах Kubernetes-кластера. Щоб зробити контейнер hello-node доступним назовні віртуальної мережі Kubernetes, вам потрібно подати Pod як Service Kubernetes.

  1. Скористайтесь командою kubectl expose для експонування Podʼа:

    kubectl expose deployment hello-node --type=LoadBalancer --port=8080
    

    Прапорець --type=LoadBalancer вказує, що ви хочете надати доступ до вашого Serviceʼу назовні кластера.

    Код застосунку всередині тестового образу контейнера тільки прослуховує порт 8080. Якщо ви використовуєте інший порт в kubectl expose, клієнти не зможуть отримати доступ до вашого застосунку.

  2. Перевірте, чи створено Service:

    kubectl get services
    

    Ви маєте отримати вивід, подібний до такого:

    NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
    hello-node   LoadBalancer   10.108.144.78   <pending>     8080:30369/TCP   21s
    kubernetes   ClusterIP      10.96.0.1       <none>        443/TCP          23m
    

    Хмарні провайдери, які підтримують балансувальники навантаження, зовнішні IP-адреси можуть надаватись для доступу до Serviceʼу. В minikube LoadBalancer створює Service, доступний через команду minikube service.

  3. Виконайте наступну команду:

    minikube service hello-node
    

    Це відкриє вікно вебоглядача, що показує відповідь застосунку.

Увімкнення надбудов

Інструменти minikube містять набір вбудованих надбудов, які можна увімкнути, вимкнути та відкрити в локальному оточені Kubernetes.

  1. Перегляньте список доступних надбудов:

    minikube addons list
    

    Ви маєте отримати вивід, подібний до такого:

    addon-manager: enabled
    dashboard: enabled
    default-storageclass: enabled
    efk: disabled
    freshpod: disabled
    gvisor: disabled
    helm-tiller: disabled
    ingress: disabled
    ingress-dns: disabled
    logviewer: disabled
    metrics-server: disabled
    nvidia-driver-installer: disabled
    nvidia-gpu-device-plugin: disabled
    registry: disabled
    registry-creds: disabled
    storage-provisioner: enabled
    storage-provisioner-gluster: disabled
    
  2. Увімкніть надбудову, наприклад, metrics-server:

    minikube addons enable metrics-server
    

    Ви маєте отримати вивід, подібний до такого:

    The 'metrics-server' addon is enabled
    
  3. Перегляньте Podʼи та Serviceʼи, які щойно було створено:

    kubectl get pod,svc -n kube-system
    

    Вивід має бути подібним до такого:

    NAME                                        READY     STATUS    RESTARTS   AGE
    pod/coredns-5644d7b6d9-mh9ll                1/1       Running   0          34m
    pod/coredns-5644d7b6d9-pqd2t                1/1       Running   0          34m
    pod/metrics-server-67fb648c5                1/1       Running   0          26s
    pod/etcd-minikube                           1/1       Running   0          34m
    pod/influxdb-grafana-b29w8                  2/2       Running   0          26s
    pod/kube-addon-manager-minikube             1/1       Running   0          34m
    pod/kube-apiserver-minikube                 1/1       Running   0          34m
    pod/kube-controller-manager-minikube        1/1       Running   0          34m
    pod/kube-proxy-rnlps                        1/1       Running   0          34m
    pod/kube-scheduler-minikube                 1/1       Running   0          34m
    pod/storage-provisioner                     1/1       Running   0          34m
    
    NAME                           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
    service/metrics-server         ClusterIP   10.96.241.45    <none>        80/TCP              26s
    service/kube-dns               ClusterIP   10.96.0.10      <none>        53/UDP,53/TCP       34m
    service/monitoring-grafana     NodePort    10.99.24.54     <none>        80:30002/TCP        26s
    service/monitoring-influxdb    ClusterIP   10.111.169.94   <none>        8083/TCP,8086/TCP   26s
    
  4. Перевірте вивід з metrics-server:

    kubectl top pods
    

    Ви маєте отримати вивід, подібний до такого:

    NAME                         CPU(cores)   MEMORY(bytes)   
    hello-node-ccf4b9788-4jn97   1m           6Mi             
    

    Якщо ви бачите наступне повідомлення, почекайте та спробуйте ще раз:

    error: Metrics API not available
    
  5. Вимкніть metrics-server:

    metrics-server was successfully disabled
    

Видалення кластера minikube

Тепер ви можете видалити ресурси, які ви створили в кластері:

kubectl delete service hello-node
kubectl delete deployment hello-node

Зупиніть кластер minikube:

minikube stop

Необовʼязково, ви можете видалити кластер minikube:

minikube delete

Якщо ви бажаєте використовувати minikube знову для продовження вивчення Kubernetes, ви можете не видаляти його.

Підсумки

Ця сторінка містить базові аспекти використання minikube для розгортання простого кластера Kubernetes та запуску тестового застосунку. Тепер ви готові до розгортання власних застосунків.

Що далі

2 - Ознайомлення з основами Kubernetes

Основи Kubernetes

Цей посібник надає інструкції з основ системи оркестрування кластерів Kubernetes. Кожний модуль містить деяку вступну інформацію про основні функції та концепції Kubernetes, а також практичний посібник, за яким ви можете вчитися.

За допомогою цих посібників ви дізнаєтесь:

  • як розгорнути контейнеризований застосунок в кластері.
  • як масштабувати Deployment.
  • як розгорнути нову версію контейнеризованого застосунку.
  • як налагодити контейнеризований застосунок.

Чим Kubernetes може бути корисний для вас?

З сучасними вебсервісами користувачі очікують доступності застосунків 24/7, а розробники прагнуть розгортати нові версії цих застосунків кілька разів на день. Контейнеризація допомагає упаковувати програмне забезпечення для досягнення цих цілей, дозволяючи випускати оновлення застосунків без перерви в роботі. Kubernetes допомагає забезпечити те, що контейнеризовані застосунки працюватимуть там і тоді, де це потрібно, та надає їм необхідні ресурси та інструменти для ефективної роботи. Kubernetes — це готова до використання, відкрита платформа, розроблена на основі здобутого Google досвіду в оркеструванні контейнерів, поєднаного з найкращими ідеями та практиками спільноти.


2.1 - Створення кластера

Дізнайтесь про кластери Kubernetes та створіть простий кластер за допомогою Minikube.

2.1.1 - Використання Minikube для створення кластера

Дізнайтесь, що таке Kubernetes кластер. Дізнайтесь, що таке Minikube. Запустіть кластер Kubernetes.

Мета

  • Дізнатись, що таке кластер Kubernetes.
  • Дізнатись, що таке Minikube.
  • Запустити Kubernetes кластер на вашому компʼютері.

Kubernetes кластери

Kubernetes організовує високодоступний кластер компʼютерів, які взаємодіють, працюючи як єдиний блок. Абстракції в Kubernetes дозволяють розгортати контейнеризовані застосунки в кластері, не привʼязуючи їх специфічно до окремих машин. Щоб скористатися цією новою моделлю розгортання, застосунки повинні бути упаковані таким чином, що відділяє їх від конкретних хостів: вони повинні бути контейнеризовані. Контейнеризовані застосунки є більш гнучкими та доступними, ніж у минулих моделях розгортання, коли застосунки встановлювалися безпосередньо на конкретні машини як пакунки, глибоко інтегровані в хост. Kubernetes автоматизує розподіл та планування контейнерів застосунків у кластері більш ефективним способом. Kubernetes є платформою з відкритим кодом і готовою до операційного використання.

Кластер Kubernetes складається з двох типів ресурсів:

  • Панелі управління (Control Plane), що координує роботу кластера
  • Вузлів (Nodes) — робочіх машин, на яких запущені застосунки

Зміст:

  • Кластер Kubernetes
  • Minikube

Kubernetes — це платформа з відкритим кодом промислового класу, яка оркеструє розташування (планування) та виконання контейнерів застосунків в межах та поміж компʼютерними кластерами.


Схема кластера


Панель управління (Control Plane) відповідає за керування кластером. Вона координує всі процеси у вашому кластері, такі як запуск застосунків, підтримка їх бажаного стану, масштабування застосунків та розгортання оновлень.

Вузол (Node) — це ВМ або фізичний компʼютер, що виступає у ролі робочої машини в кластері Kubernetes. Кожен вузол має kubelet — агента для управління вузлом та обміну даними з панеллю управління Kubernetes. Також на вузлі мають бути встановлені інструменти для виконання операцій з контейнерами, такі як containerd або CRI-O. Кластер Kubernetes, що обслуговує операційний трафік має складатися як мінімум із трьох вузлів. Якщо один вузол вийде з ладу обидва члени etcd та панель управління будуть втрачені, а надмірність буде порушена. Ви можете зменшити ризик додаванням більше одного вузла з панеллю управління.

Панелі управління керують кластером, а вузли призначені для запуску застосунків.

Коли ви розгортаєте застосунки у Kubernetes, ви наказуєте панелі управління запустити контейнери застосунку. Панель управління розподіляє (планує) контейнери для запуску на вузлах кластера. Компоненти на рівні вузла, такі, як kubelet, спілкуються з панеллю управління за допомогою API Kubernetes, який надається панеллю управління. Кінцеві користувачі також можуть використовувати API Kubernetes для взаємодії з кластером.

Кластер Kubernetes можна розгорнути як на фізичних, так й на віртуальних машинах. Щоб розпочати розробку під Kubernetes, ви можете скористатися Minikube — спрощеною реалізацією Kubernetes. Minikube створює на вашому локальному компʼютері ВМ, на якій розгортає простий кластер з одного вузла. Існують версії Minikube для операційних систем Linux, macOS та Windows. Minikube CLI надає основні операції для роботи з вашим кластером, такі як start, stop, status та delete.

Тепер ви знаєте, що таке Kubernetes. Тож відвідаємо сторінку Привіт Minikube та спробуємо його на вашому компʼютері.

2.2 - Розгортання застосунку

2.2.1 - Використання kubectl для створення Deploymentʼа

Дізнайтесь, що таке Deployment застосунків. Розгорніть свій перший застосунок у Kubernetes за допомогою kubectl.

Мета

  • Дізнатися, що таке Deployment застосунків.
  • Розгорнути свій перший застосунок у Kubernetes за допомогою kubectl.

Процеси Kubernetes Deployment

Після створення працюючого кластера Kubernetes ви можете розгортати свої контейнеризовані застосунки на його основі. Для цього створіть розгортання (Deployment) Kubernetes. Deployment вказує Kubernetes, як створювати та оновлювати екземпляри вашого застосунку. Після створення Deployment панель управління Kubernetes розподіляє екземпляри застосунків, що включені в цей Deployment, для запуску на окремих вузлах в кластері.

Коли екземпляри застосунків створені, контролер розгортання Kubernetes безперервно відстежує їх стан. Якщо вузол, на якому запущено екземпляр, вимикається або видаляється, контролер розгортання замінює екземпляр новим на іншому вузлі в кластері. Це забезпечує механізм самовідновлення для розвʼязання проблем з відмовою або обслуговуванням вузлів.

У світі до-оркестрування часто використовувалися скрипти встановлення для запуску застосунків, але вони не дозволяли відновлення після відмови вузла. Шляхом як створення екземплярів застосунків, так і утримання їх в робочому стані на різних вузлах, розгортання Kubernetes забезпечує фундаментально відмінний підхід до управління застосунками.

Зміст:

  • Deployment'и
  • Kubectl

Deployment відповідає за створення та оновлення Podʼів для вашого застосунку


Розгортання вашого першого застосунку в Kubernetes


Ви можете створювати та управляти розгортанням (Deployment) за допомогою інтерфейсу командного рядка Kubernetes, kubectl. Kubectl використовує API Kubernetes для взаємодії з кластером. У цьому модулі ви вивчите найпоширеніші команди kubectl, які потрібні для створення розгортань та запуску застосунків в кластері Kubernetes.

Коли ви створюєте Deployment, вам необхідно вказати образ контейнера вашого застосунку та скільки його реплік ви бажаєте запустити. Згодом цю інформацію можна змінити, оновивши Deployment. В навчальних модулях 5 і 6 йдеться про те, як масштабувати і оновлювати Deployment'и.

Для того, щоб розгортати застосунки в Kubernetes, їх потрібно упакувати в один із підтримуваних форматів контейнерів

Для вашого першого розгортання ви будете використовувати застосунок hello-node, який упакований в Docker-контейнер і використовує NGINX для відгуку на всі запити. (Якщо ви ще не пробували створити застосунок hello-node та розгортати його за допомогою контейнера, ви можете це зробити, слідуючи інструкціям з посібника Привіт Minikube).

Вам також потрібно мати `kubectl`. Якщо ви ще не встановили його, відвідайте встановлення інструментів.

Тепер ви знаєте, що таке Deployment. Тож розгорнемо ваш перший застосунок!


Основи kubectl

Загальний формат команди kubectl: kubectl дія ресурс

Тут виконується вказана дія (наприклад, create, describe або delete) для вказаного ресурсу (наприклад, node або deployment). Ви можете використовувати --help після команд для отримання додаткової інформації про можливі параметри (наприклад, kubectl get nodes --help).

Перевірте, що kubectl налаштовано для роботи з вашим кластером, виконавши команду kubectl version.

Перевірте, що kubectl встановлено і ви можете бачити як версію клієнта, так і версію сервера.

Щоб переглянути вузли в кластері, виконайте команду kubectl get nodes.

Ви побачите доступні вузли. Пізніше Kubernetes вибере, де розгорнути наш застосунок на основі ресурсів, доступних на вузлах.

Розгортання застосунку

Розгорнімо наш перший застосунок на Kubernetes за допомогою команди kubectl create deployment. Ми повинні вказати назву розгортання та розташування образу застосунку (вкажіть повну URL-адресу репозиторію образів, розміщених за межами Docker Hub).

kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1

Відмінно! Ви щойно розгорнули свій перший застосунок, створивши Deployment. Це призвело до виконання для вас кількох дій:

  • пошук відповідного вузла, де може бути запущений екземпляр застосунку (у нас доступний лише 1 вузол)
  • планування запуску застосунку на цьому вузлі
  • налаштування кластера для перепланування екземпляра на новому вузлі за необхідності

Щоб переглянути ваші розгортання, використовуйте команду kubectl get deployments:

kubectl get deployments

Ми бачимо, що є 1 розгортання, яке запускає один екземпляр вашого застосунку. Екземпляр працює в контейнері на вашому вузлі.

Перегляд застосунку

Podʼи, які працюють всередині Kubernetes, запущені в ізольованій приватній мережі. Типово вони видимі з інших Podʼів та служб всередині того ж самого кластера Kubernetes, але не за межами цієї мережі. Коли ми використовуємо kubectl, ми взаємодіємо через точку доступу API для спілкування з нашим застосунком.

Розглянемо інші варіанти того, як вивести наш застосунок за межі кластера Kubernetes пізніше, в Модулі 4. Також, як у базовому посібнику, тут ми не пояснюємо докладно, що таке Podʼи, це буде розглянуто в подальших темах.

Команда kubectl proxy може створити проксі, який буде пересилати дані в мережу, що охоплює весь кластер. Роботу проксі можна завершити, натиснувши control-C, і він не виводитиме жодного результату під час роботи.

Вам потрібно відкрити друге вікно термінала для запуску проксі.

kubectl proxy

Тепер у нас є зʼєднання між вашим хостом (терміналом) та кластером Kubernetes. Проксі дозволяє безпосередній доступ до API з цих терміналів.

Ви можете побачити всі ці API, розміщені через проксі-endpoint. Наприклад, ми можемо запитати версію напряму через API, використовуючи команду curl:

curl http://localhost:8001/version

Сервер API автоматично створює точку доступу для кожного Podʼа на основі імені Podʼа, яка також доступна через проксі.

Спочатку нам потрібно отримати імʼя Podʼа, і ми збережемо його в змінну оточення POD_NAME:

export POD_NAME=$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')
echo Name of the Pod: $POD_NAME

Ви можете отримати доступ до Podʼа через проксі-API, запустивши:

curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME:8080/proxy/

Для того, щоб нове розгортання було доступним без використання проксі, потрібен сервіс, про що буде розказано в Модулі 4.

Якщо ви готові, перейдіть до Перегляд Podʼів та Nodeʼів.

2.3 - Дослідження застосунку

2.3.1 - Перегляд Podʼів та вузлів (Node)

Дізнайтесь, як розвʼязувати проблеми з розгорнутими застосунками, використовуючи kubectl get, kubectl describe, kubectl logs та kubectl exec.

Мета

  • Дізнатися, що таке Podʼи Kubernetes.
  • Дізнатися, що таке вузли Kubernetes.
  • Діагностика розгорнутих застосунків.

Podʼи Kubernetes

Коли ви створили Deployment у модулі 2, Kubernetes створив Pod, щоб розмістити ваш застосунок. Pod — це абстракція в Kubernetes, що являє собою групу з одного або декількох контейнерів застосунку (таких як Docker) та ресурсів, спільних для цих контейнерів. До цих ресурсів належать:

  • Спільні сховища даних, або Volumes
  • Мережа, адже кожен Pod у кластері має унікальну IP-адресу
  • Інформація про запуск кожного контейнера, така як версія образу контейнера або використання певних портів

Pod моделює специфічний для даного застосунку "логічний хост" та може містити різні, але доволі щільно звʼязані один з одним контейнери. Наприклад, в одному Podʼі може бути контейнер з вашим Node.js застосунком та інший контейнер, що передає дані для публікації Node.js вебсерверу. Контейнери в межах Podʼа мають спільну IP-адресу та порти, завжди є сполученими, плануються для запуску разом та запускаються у спільному контексті на одному вузлі.

Pod є неподільною одиницею платформи Kubernetes. Коли ви створюєте Deployment у Kubernetes, цей Deployment створює Podʼи вже з контейнерами всередині, на відміну від створення контейнерів окремо. Кожен Pod привʼязаний до вузла, до якого його було розподілено, і лишається на ньому до припинення роботи (згідно з політикою перезапуску) або видалення. У разі відмови вузла ідентичні Podʼи розподіляються по інших доступних вузлах кластера.

Зміст:

  • Podʼи
  • Вузли
  • Основні команди kubectl

Pod — це група з одного або декількох контейнерів (таких як Docker), що має спільне сховище даних (volumes), унікальну IP-адресу і містить інформацію про те, як їх запустити.


Узагальнена схема Podʼів


Вузли

Pod завжди запускається на вузлі. Вузол — це робоча машина в Kubernetes, віртуальна або фізична, залежно від кластера. Функціонування кожного вузла контролюється панеллю управління. Вузол може мати декілька Podʼів, а панель управління автоматично призначає Podʼи вузлам в кластері. Панель управління Kubernetes автоматично розподіляє Podʼи по вузлах кластера з урахуванням ресурсів, наявних на кожному вузлі.

На кожному вузлі Kubernetes запущені як мінімум:

  • Kubelet — процес, що забезпечує обмін даними між панеллю управління Kubernetes та робочим вузлом; kubelet керує Podʼами та контейнерами, запущеним на машині.
  • Оточення для запуску контейнерів (таке як Docker) забезпечує завантаження образу контейнера з реєстру, розпакування контейнера та запуск застосунку.

Контейнери повинні бути разом в одному Podʼі, лише якщо вони щільно звʼязані і мають спільні ресурси, такі як диск.


Узагальнена схема вузлів


Виправлення проблем за допомогою kubectl

У модулі 2 ви використовували інтерфейс командного рядка kubectl. Ви будете продовжувати його використовувати в модулі 3 для отримання інформації про розгорнуті застосунки та їхні середовища. За допомогою наступних підкоманд kubectl можна виконати найбільш поширені операції:

  • kubectl get — показати перелік ресурсів
  • kubectl describe — виведення детальної інформації про ресурс
  • kubectl logs — виведення логів контейнера в Podʼі
  • kubectl exec — виконання команди в контейнері в Podʼі

Ви можете використовувати ці команди, щоб переглядати інформацію про те, коли були розгорнуті застосунки, який їхній поточний статус, де вони запущені та які їхні конфігурації.

Тепер, коли ми більше знаємо про компоненти нашого кластера та командний рядок, дослідімо наш застосунок.

Вузол — це робоча машина в Kubernetes і може бути віртуальною машиною або фізичною машиною залежно від кластера. На одному вузлі може працювати кілька Podʼів.

Перевірка конфігурації застосунку

Перевірмо, чи працює застосунок, який ми розгорнули в попередньому сценарії. Ми використаємо команду kubectl get і подивимося на наявні Podʼи:

kubectl get pods

Якщо Podʼи не запущені, зачекайте кілька секунд і знову спробуйте вивести список Podʼів. Ви можете продовжити, якщо ви бачите, що працює один Pod.

Далі, щоб переглянути, які контейнери є всередині цього Podʼа і які образи використовуються для створення цих контейнерів, ми використовуємо команду kubectl describe pods:

kubectl describe pods

Тут ми бачимо деталі про контейнер Podʼа: IP-адресу, порти та список подій, повʼязаних з життєвим циклом Podʼа.

Вивід підкоманди describe є розлогим і охоплює деякі концепції, які ми ще не пояснили, але не хвилюйтеся, вони стануть зрозумілими до кінця цього курсу.

Примітка: підкоманду describe можна використовувати для отримання детальної інформації про більшість примітивів Kubernetes, включаючи вузли, Podʼи та Deployment. Вивід команди describe призначений для сприйняття людиною, а не для сценаріїв.

Показати застосунок у терміналі

Пригадайте, що Podʼи працюють в ізольованій, приватній мережі — тому нам потрібно налаштувати проксі-доступ до них для налагодження та взаємодії з ними. Для цього ми використовуємо команду kubectl proxy для запуску проксі в іншому терміналі. Відкрийте нове вікно термінала і введіть у цьому новому терміналі:

kubectl proxy

Тепер ми знову отримаємо імʼя Podʼа та будемо звертатись до цього Podʼа безпосередньо через проксі. Щоб отримати імʼя Podʼа та зберегти його в змінну середовища POD_NAME:

export POD_NAME="$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')"
echo Name of the Pod: $POD_NAME

Щоб переглянути вивід нашого застосунку, виконайте запит curl:

curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME:8080/proxy/

URL — це шлях до API Podʼа.

Перегляд логів контейнера

Все, що застосунок зазвичай виводить на стандартний вивід, стає логами контейнера всередині Podʼа. Ми можемо отримати ці логи, використовуючи команду kubectl logs:

kubectl logs "$POD_NAME"

Примітка: Нам не потрібно вказувати імʼя контейнера, оскільки у нас є лише один контейнер всередині Podʼа.

Виконання команди в контейнері

Ми можемо виконувати команди безпосередньо в контейнері після того, як Pod буде запущено та він працюватиме. Для цього ми використовуємо підкоманду exec і вказуємо імʼя Podʼа як параметр. Перегляньмо змінні середовища:

kubectl exec "$POD_NAME" -- env

Знову варто зазначити, що можна пропустити імʼя самого контейнера, оскільки у нас є лише один контейнер в Podʼі.

Далі розпочнім сеанс bash в контейнері Podʼа:

kubectl exec -ti $POD_NAME -- bash

Тепер у нас є відкрита консоль в контейнері, де запущений наш застосунок NodeJS. Вихідний код застосунку знаходиться у файлі server.js:

cat server.js

Ви можете перевірити, чи застосунок запущено, виконавши команду curl:

curl http://localhost:8080

Примітка: тут ми використовували localhost, оскільки ми виконали команду всередині Podʼа NodeJS. Якщо ви не можете підʼєднатися до localhost:8080, перевірте, чи ви виконали команду kubectl exec і запускаєте команду зсередини Podʼа

Щоб закрити підключення до контейнера, введіть exit.

Як тільки ви будете готові, переходьте до Використання Service для надання доступу до вашого застосунку.

2.4 - Доступ до застосунку зовні

2.4.1 - Використання Service для доступу до застосунку

Дізнайтесь про Service у Kubernetes. Ознайомтесь з тим, як мітки та селектори повʼязані з Service. Відкрийте доступ до застосунку по за межами Kubernetes кластера.

Мета

  • Дізнатись про Service у Kubernetes.
  • Ознайомитись з тим, як мітки та селектори повʼязані з Service.
  • Відкрити доступ до застосунку по за межами Kubernetes кластера.

Огляд Сервісів в Kubernetes

Існування Podʼів в Kubernetes є обмеженими за часом, також вони мають свій життєвий цикл. Коли робочий вузол припиняє існування, також втрачаються Podʼи, які виконуються на цьому вузлі. ReplicaSet може динамічно приводити кластер до бажаного стану шляхом створення нових Podʼів, щоб ваш застосунок продовжував працювати. Наприклад, розгляньмо обробник зображень, що має 3 копії. Ці копії (репліки) можуть бути взаємозамінні; система форонтенду не повинна перейматися репліками бекенду або навіть тим, чи Pod втрачено та створено наново. Проте кожен Pod в кластері Kubernetes має унікальну IP-адресу, навіть Podʼи на одному вузлі, тому потрібно мати спосіб автоматичного узгодження змін серед Podʼів, щоб ваші застосунки продовжували працювати.

Service в Kubernetes є абстракцією, яка визначає логічний набір Podʼів та політику доступу до них. Serviceʼи забезпечують слабку звʼязаність між залежними Podʼами. Service визначається за допомогою YAML або JSON, так само як і всі обʼєкти Kubernetes. Набір Podʼів, на які призначений Service, зазвичай визначається селектором міток (label selector) (див. нижче, чому selector іноді не включають у специфікацію Service).

Хоча кожний Pod має унікальну IP-адресу, ці IP не доступні за межі кластера без використання Service. Serviceʼи уможливлюють надходження трафіку до ваших застосунків. Serviceʼи можуть бути оприлюднені у різний спосіб, за допомогою type у spec Service:

  • ClusterIP (типове значення) — відкриває доступ до внутрішнього IP Service в кластері. Цей тип робить Service доступним лише зсередини кластера.
  • NodePort — відкриває доступ до Service на тому ж порті кожного обраного вузла в кластері за допомогою NAT. Робить Service доступним ззовні кластера за допомогою <NodeIP>:<NodePort>. Є надмножиною відносно ClusterIP.
  • LoadBalancer — створює зовнішній балансувальник навантаження в поточному хмарному середовищі (якщо підтримується) та призначає фіксовану зовнішню IP-адресу для Service. Є надмножиною відносно NodePort.
  • ExternalName — звʼязує Service з вмістом поля externalName (наприклад, foo.bar.example.com), повертаючи запис CNAME із його значенням. Не встановлюється жодного проксі. Цей тип вимагає v1.7 або вище kube-dns або CoreDNS версії 0.0.8 або вище.

Додаткову інформацію про різновиди Service можна знайти у посібнику Використання IP-адреси джерела. Дивіться також Підключення застосунків за допомогою Service.

Крім того, слід зауважити, що існують випадки використання Service, при яких не визначається selector у специфікації. Service, створений без selector, також не створить відповідний обʼєкт Endpoints. Це дозволяє користувачам вручну відкривати доступ Service на конкретні точки доступу. Ще одна можливість, чому може бути відсутній селектор — використання виключно type: ExternalName.

Зміст

  • Відкриття Podʼів для зовнішнього трафіка
  • Балансування навантаження трафіку між Podʼами
  • Використання міток

Service в Kubernetes – це рівень абстракції, який визначає логічний набір Podʼів і дозволяє експонування зовнішнього трафіку, балансування навантаження та виявлення служб для цих Podʼів.


Services та мітки (Labels)

Service маршрутизує трафік набору Podʼів. Service є абстракцією, яка дозволяє Podʼам зникати та реплікуватися в Kubernetes, не впливаючи на ваш застосунок. Виявлення та маршрутизація серед залежних Podʼів (таких як компоненти frontend та backend у застосунку) обробляються Serviceʼами Kubernetes.

Service визначають набір Podʼів за допомогою міток та селекторів, примітиву гуртування, який дозволяє логічно взаємодіяти з обʼєктами в Kubernetes. Мітки — це пари ключ/значення, призначені обʼєктам та можуть бути використані різними способами:

  • Призначення обʼєктів до оточення розробки, тестування та експлуатації
  • Вбудовування теґів версій
  • Класифікація обʼєкта за допомогою теґів


Мітки можна прикріплювати до обʼєктів при їх створенні або пізніше. Їх можна змінювати в будь-який час. Давайте зараз використаємо Service для надання доступу до нашого застосунку та застосуємо деякі мітки.

Крок 1: Створення нового Service

Перевіримо, що наш застосунок працює. Ми використовуватимемо команду kubectl get та переглядатимемо наявні Podʼи:

kubectl get pods

Якщо Podʼи не запущені, це означає, що обʼєкти з попередніх кроків були видалені. У цьому випадку поверніться та створіть Deployment з посібника Використання kubectl для створення Deployment. Зачекайте кілька секунд та перегляньте список Podʼів знову. Ви можете продовжити, як тільки побачите, що один Pod працює.

Далі виведемо список наявних Serviceʼів у нашому кластері:

kubectl get services

У нас є Service з назвою kubernetes, яка створюється стандартно, коли minikube запускає кластер. Щоб створити новий Service та зробити зовнішній трафік доступним для нього, ми використовуватимемо команду expose з параметром NodePort.

kubectl expose deployment/kubernetes-bootcamp --type="NodePort" --port 8080

Давайте знову запустимо команду get services:

kubectl get services

Тепер у нас є робочий Service з назвою kubernetes-bootcamp. Тут ми бачимо, що Service отримав унікальний IP в кластері, внутрішній порт та зовнішній IP (IP вузла).

Щоб дізнатися, який порт був відкритий ззовні (для Serviceʼу з type: NodePort), ми запустимо команду describe service:

kubectl describe services/kubernetes-bootcamp

Створимо змінну середовища з назвою NODE_PORT, яка матиме значення порту вузла:

export NODE_PORT="$(kubectl get services/kubernetes-bootcamp -o go-template='{{(index .spec.ports 0).nodePort}}')"
echo "NODE_PORT=$NODE_PORT"

Тепер ми можемо перевірити, що застосунок отримав доступ за межі кластера, використовуючи curl, IP-адресу вузла та зовнішній порт:

curl http://"$(minikube ip):$NODE_PORT"

І, ми отримуємо відповідь від сервера. Service — працює.

Крок 2: Використання міток

Deployment автоматично створив мітку для нашого Podʼа. За допомогою команди describe deployment ви можете побачити імʼя (ключ) цієї мітки:

kubectl describe deployment

Використаємо цю мітку для отримання списку наших Podʼів. Ми використовуватимемо команду kubectl get pods з -l як параметр, за яким слідують значення мітки:

kubectl get pods -l app=kubernetes-bootcamp

Ви можете зробити те саме, щоб вивести список поточних служб:

kubectl get services -l app=kubernetes-bootcamp

Отримайте назву Podʼа та збережіть її в змінній середовища POD_NAME:

export POD_NAME="$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')"
echo "Name of the Pod: $POD_NAME"

Для застосування нової мітки ми використовуємо команду label за якою слідує тип обʼєкта, назва обʼєкта та нова мітка:

kubectl label pods "$POD_NAME" version=v1

Це додасть нову мітку до нашого Podʼа (ми закріпили версію програми для Podʼа), і ми можемо перевірити це за допомогою команди describe pod:

kubectl describe pods "$POD_NAME"

Тут ми бачимо, що мітка тепер прикріплена до нашого Podʼа. І тепер ми можемо отримати список Podʼів, використовуючи нову мітку:

kubectl get pods -l version=v1

І, ми бачимо Pod.

Крок 3: Видалення Service

Для видалення Service ви можете використовувати команду delete service. Тут також можна використовувати мітки:

kubectl delete service -l app=kubernetes-bootcamp

Переконайтеся, що Service видалено:

kubectl get services

Це підтверджує, що наш Service видалено. Щоб переконатися, що маршрут більше не відкритий, ви можете перевірити раніше відкриті IP та порт за допомогою curl:

curl http://"$(minikube ip):$NODE_PORT"

Це доводить, що застосунок більше не доступний ззовні кластера. Ви можете переконатись, що застосунок все ще працює за допомогою curl зсередини Podʼа:

kubectl exec -ti $POD_NAME -- curl http://localhost:8080

Тут ми бачимо, що застосунок працює. Це тому, що Deployment управляє застосунком. Для припинення роботи застосунку вам слід видалити також й Deployment.

Одразу, як тільки ви будете готові, переходьте до Запуску кількох екземплярів вашого застосунку.

2.5 - Масштабування застосунку

2.5.1 - Запуск кількох екземплярів вашого застосунку

Масштабування застосунку вручну за допомогою kubectl.

Мета

  • Масштабування застосунку за допомогою kubectl.

Масштабування Застосунку

Раніше ми створили Deployment, а потім робили його загальнодоступним за допомогою Service. Deployment створив лише один Pod для запуску нашого pfcnjceyre. Зі збільшенням трафіку, нам потрібно масштабувати застосунок, щоб відповідати зростаючим вимогам користувачів.

Якщо ви ще не працювали над попередніми розділами, почніть з Використання minikube для створення кластера.

Масштабування досягається зміною кількості реплік в Deployment.


Зміст:

  • Масштабування Deployment

Ви можете створити Deployment з початку з декількома екземплярами, використовуючи параметр --replicas для команди створення Deployment kubectl

Огляд масштабування


Масштабування Deployment гарантує створення нових Podʼів і їх призначення на Вузли з вільними ресурсами. Масштабування збільшить кількість Podʼів до нового бажаного стану. Kubernetes також підтримує автоматичне масштабування Podʼів, але це виходить за рамки цього посібника. Також можливе масштабування до нуля, і це призведе до завершення всіх Podʼів вказаного Deployment.

Запуск кількох екземплярів застосунку вимагає засобів для розподілу трафіку між ними. У Service є вбудований балансер навантаження, який розподілить мережевий трафік на всі Podʼи, що виставлені Deployment. Service будуть постійно відстежувати робочі Podʼи, використовуючи точки доступу, щоб гарантувати, що трафік направляється лише на доступні Podʼи.

Масштабування досягається зміною кількості реплік в Deployment.


Якщо у вас є кілька екземплярів застосунку, ви зможете робити оновлення без перерв. Ми розглянемо це в наступному розділі посібника. Тепер перейдемо до термінала та масштабуємо наш застосунок.

Масштабування Deployment

Щоб переглянути свій Deployment, використовуйте команду get deployments:

kubectl get deployments

Вивід повинен бути схожий на:

    NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
    kubernetes-bootcamp   1/1     1            1           11m
    

Маємо 1 Pod. Якщо цього немає, виконайте команду ще раз. Тут маємо:

  • NAME — назви Deployment в кластері.
  • READY — показує співвідношення поточних/бажаних реплік
  • UP-TO-DATE — показує кількість реплік, які були оновлені для досягнення бажаного стану.
  • AVAILABLE — показує, скільки реплік застосунку доступні користувачам.
  • AGE — показує час, протягом якого застосунок працює.

Щоб переглянути ReplicaSet, створений Deployment, виконайте:

kubectl get rs

Зверніть увагу, що назва ReplicaSet завжди форматується як [DEPLOYMENT-NAME]-[ВИПАДКОВИЙ-РЯДОК]. Випадковий рядок генерується випадковим чином та використовує pod-template-hash як основу для створеня.

Два важливі стовпці цього виводу:

  • DESIRED — показує бажану кількість реплік застосунку, яку ви визначаєте під час створення Deployment. Це бажаний стан.
  • CURRENT — показує, скільки реплік в цей час працюють.

Далі масштабуймо Deployment до 4 реплік. Ми використаємо команду kubectl scale, за якою слідує тип Deployment, назва та бажана кількість екземплярів:

kubectl scale deployments/kubernetes-bootcamp --replicas=4

Щоб знову переглянути свої Deployment, використовуйте get deployments:

kubectl get deployments

Зміни були застосовані, і у нас є 4 екземпляри застосунку. Далі перевірмо, чи змінилася кількість Podʼів:

kubectl get pods -o wide

Тепер є 4 Podʼа з різними IP-адресами. Зміна була зареєстрована в журналі подій Deployment. Щоб перевірити це, використовуйте команду describe:

kubectl describe deployments/kubernetes-bootcamp

Ви також можете побачити, що у виводі цієї команди тепер є 4 репліки.

Балансування навантаження

Перевіримо, чи Service балансує трафік. Щоб дізнатися зовнішній IP та порт, ми можемо використовувати команду describe, як ми дізнались в попередній частині посібника:

kubectl describe services/kubernetes-bootcamp

Створіть змінну середовища з іменем NODE_PORT, яка має значення як порт Вузла:

export NODE_PORT="$(kubectl get services/kubernetes-bootcamp -o go-template='{{(index .spec.ports 0).nodePort}}')"

echo NODE_PORT=$NODE_PORT

Далі ми запустимо curl з зовнішньою IP-адресою та портом. Виконайте команду кілька разів:

curl http://"$(minikube ip):$NODE_PORT"

Ми потрапляємо на різні Podʼи при кожному запиті. Це демонструє, що балансування навантаження працює.

Вивід має бути схожим на:

Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-644c5687f4-wp67j | v=1
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-644c5687f4-hs9dj | v=1
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-644c5687f4-4hjvf | v=1
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-644c5687f4-wp67j | v=1
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-644c5687f4-4hjvf | v=1
                     

Зменшення розгортання

Щоб зменшити Deployment до 2 реплік, знову запустіть команду scale:

kubectl scale deployments/kubernetes-bootcamp --replicas=2

Перевірте Deployment, щоб переконатися, що зміни були застосовані, за допомогою команди get deployments:

kubectl get deployments

Кількість реплік зменшилася до 2. Перегляньте кількість Podʼів за допомогою get pods:

kubectl get pods -o wide

Це підтверджує, що роботу двох Podʼів було завершено.

Якщо ви готові, переходьте до Виконання поступового оновлення.

2.6 - Оновлення застосунку

2.6.1 - Виконання поступового оновлення

Виконання поетапного оновлення застосунку (rolling update) за допомогою kubectl.

Мета

  • Виконати розгортання з оновленням за допомогою kubectl.

Оновлення застосунку

Користувачі очікують, що застосунки будуть доступні цілодобово, і очікується, що розробники розгортатимуть нові версії кілька разів на день. У Kubernetes це робиться за допомогою розгортань з поступовим оновленням. Поступове оновлення (rolling update) дозволяє виконати оновлення Deployment без перерви в роботі застосунку. Це досягається поетапною заміною поточних Podʼів новими. Нові Podʼи призначаються вузлам з вільними ресурсами, а Kubernetes чекає, доки ці нові Podʼи не почнуть працювати, перш ніж вилучити старі Podʼи.

У попередньому розділі ми масштабували наш застосунок для запуску кількох екземплярів. Це є вимогою для виконання оновлень без впливу на доступність застосунку. Стандартно максимальна кількість Podʼів, які можуть бути недоступні під час оновлення, та максимальна кількість нових Podʼів, які можуть бути створені, дорівнює одному. Обидві опції можуть бути налаштовані як у вигляді точної кількості, так і у відсотках (від усіх Podʼів). У Kubernetes оновлення мають версії, і будь-яке оновлення Deployment може бути повернуте до попередньої (стабільної) версії.

Зміст:

  • Оновлення застосунку

Поступові оновлення дозволяють виконувати оновлення Deployment без зупинки роботи застосунку за допомогою поетапного оновлення екземплярів Podʼів.


Огляд поступового оновлення


Аналогічно масштабуванню застосунку, якщо Deployment є публічно доступним, Service буде балансувати трафік лише на доступні Podʼи під час оновлення. Доступний Pod — це екземпляр, який доступний користувачам застосунку.

Поступові оновлення дозволяють виконувати наступні дії:

  • Просування застосунку з одного середовища в інше (за допомогою оновлень образів контейнерів)
  • Відкат до попередніх версій
  • Неперервна інтеграція та постійна доставка застосунків без перерви в роботі

Якщо Deployment публічно доступний, Service буде балансувати трафік лише на доступні Podʼи під час оновлення.


У наступному інтерактивному практикумі ми оновимо наш застосунок до нової версії та виконаємо відкат.


Оновлення версії застосунку

Для виведення списку Deploymentʼів виконайте команду get deployments: kubectl get deployments

Для виведення списку запущених Podʼів виконайте команду get pods:

kubectl get pods

Для перегляду поточної версії образу застосунку виконайте команду describe pods та шукайте поле Image:

kubectl describe pods

Для оновлення образу застосунку до версії 2 використовуйте команду set image, за якою йде назва Deployment та нова версія образу:

kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=docker.io/jocatalin/kubernetes-bootcamp:v2

Команда повідомляє Deployment про використання іншого образу для вашого застосунку та запускає поступове оновлення. Перевірте стан нових Podʼів та перегляньте той, що відключається, використовуючи команду get pods:

kubectl get pods

Перевірка оновлення

Спочатку перевірте, чи Service працює, бо ви могли його видалити його на попередньому кроці, виконайте kubectl describe services/kubernetes-bootcamp. Якщо його немає, створіть його знов:

kubectl expose deployment/kubernetes-bootcamp --type="NodePort" --port 8080

Створіть змінну середовища з іменем NODE_PORT зі значенням призначеного порту вузла:

export NODE_PORT="$(kubectl get services/kubernetes-bootcamp -o go-template='{{(index .spec.ports 0).nodePort}}')"
echo "NODE_PORT=$NODE_PORT"

Потім виконайте curl з зовнішньою IP-адресою та портом:

curl http://"$(minikube ip):$NODE_PORT"

Кожен раз, коли ви виконуєте команду curl, ви попадете на різні Podʼи. Зверніть увагу, що всі Podʼи зараз працюють на останній версії (v2).

Ви також можете підтвердити оновлення, використовуючи команду rollout status:

kubectl rollout status deployments/kubernetes-bootcamp

Для перегляду поточної версії образу застосунку виконайте команду describe pods:

kubectl describe pods

У полі Image перевірите, що ви використовуєте останню версію образу (v2).

Відкат оновлення

Виконаймо ще одне оновлення та спробуємо розгорнути образ з теґом v10:

kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=gcr.io/google-samples/kubernetes-bootcamp:v10

Використовуйте get deployments, щоб переглянути стан розгортання:

kubectl get deployments

Зверніть увагу, що вивід не вказує бажану кількість доступних Podʼів. Використайте команду get pods, щоб вивести список всіх Podʼів:

kubectl get pods

Зверніть увагу, що деякі Podʼи мають статус ImagePullBackOff.

Щоб отримати більше відомостей про проблему, використовуйте команду describe pods:

kubectl describe pods

У розділі Events виводу для проблемних Podʼів, зверніть увагу, що версії образу v10 немає в репозиторії.

Щоб відкотити розгортання до попередньої робочої версії, використовуйте команду rollout undo:

kubectl rollout undo deployments/kubernetes-bootcamp

Команда rollout undo повертає розгортання до попередньо відомого стану (v2 образу). Оновлення мають версії, і ви можете повернутися до будь-якого раніше відомого стану розгортання.

Використайте команду get pods, щоб знову вивести список Podʼів:

kubectl get pods

Щоб перевірити образ, розгорнутий на цих Podʼах, використайте команду describe pods:

kubectl describe pods

Розгортання знову використовує стабільну версію застосунку (v2). Відкат був успішним.

Не забудьте очистити свій локальний кластер

kubectl delete deployments/kubernetes-bootcamp services/kubernetes-bootcamp

3 - Налаштування

3.1 - Оновлення конфігурації за допомогою ConfigMap

Ця сторінка містить покроковий приклад оновлення конфігурації всередині Pod за допомогою ConfigMap і базується на завданні Налаштування Pod для використання ConfigMap. Наприкінці цього посібника ви зрозумієте, як змінити конфігурацію для запущеного застосунку. Цей посібник використовує образи alpine та nginx як приклади.

Перш ніж ви розпочнете

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Вам потрібно мати інструмент командного рядка curl для виконання HTTP-запитів з термінала або командного рядка. Якщо у вас немає curl, ви можете його встановити. Ознайомтеся з документацією для вашої операційної системи.

Цілі

  • Оновлення конфігурації через ConfigMap, змонтованого як том
  • Оновлення змінних середовища Pod за допомогою ConfigMap
  • Оновлення конфігурації через ConfigMap в багатоконтейнерному Pod
  • Оновлення конфігурації через ConfigMap у Pod, що містить контейнер Sidecar

Оновлення конфігурації через ConfigMap, змонтовану як том

Використовуйте команду kubectl create configmap для створення ConfigMap з літеральних значень:

kubectl create configmap sport --from-literal=sport=football

Нижче наведено приклад маніфесту Deployment з ConfigMap sport, змонтованим як том у єдиний контейнер Pod.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: configmap-volume
  labels:
    app.kubernetes.io/name: configmap-volume
spec:
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/name: configmap-volume
  template:
    metadata:
      labels:
        app.kubernetes.io/name: configmap-volume
    spec:
      containers:
        - name: alpine
          image: alpine:3
          command:
            - /bin/sh
            - -c
            - while true; do echo "$(date) My preferred sport is $(cat /etc/config/sport)";
              sleep 10; done;
          ports:
            - containerPort: 80
          volumeMounts:
            - name: config-volume
              mountPath: /etc/config
      volumes:
        - name: config-volume
          configMap:
            name: sport

Створіть Deployment:

kubectl apply -f https://k8s.io/examples/deployments/deployment-with-configmap-as-volume.yaml

Перевірте Podʼи цього Deployment, щоб переконатися, що вони готові (відповідно до селектору):

kubectl get pods --selector=app.kubernetes.io/name=configmap-volume

Ви повинні побачити подібний вивід:

NAME                                READY   STATUS    RESTARTS   AGE
configmap-volume-6b976dfdcf-qxvbm   1/1     Running   0          72s
configmap-volume-6b976dfdcf-skpvm   1/1     Running   0          72s
configmap-volume-6b976dfdcf-tbc6r   1/1     Running   0          72s

На кожному вузлі, де працює один із цих Pod, kubelet отримує дані для цього ConfigMap і перетворює їх на файли у локальному томі. Потім kubelet монтує цей том у контейнер, як зазначено у шаблоні Pod. Код, що виконується у цьому контейнері, завантажує інформацію з файлу та використовує її для друку звіту на stdout. Ви можете перевірити цей звіт, переглянувши логи одного з Pod у цьому Deployment:

# Виберіть один Pod, що належить до Deployment, і перегляньте його логи
kubectl logs deployments/configmap-volume

Ви повинні побачити подібний вивід:

Found 3 pods, using pod/configmap-volume-76d9c5678f-x5rgj
Thu Jan  4 14:06:46 UTC 2024 My preferred sport is football
Thu Jan  4 14:06:56 UTC 2024 My preferred sport is football
Thu Jan  4 14:07:06 UTC 2024 My preferred sport is football
Thu Jan  4 14:07:16 UTC 2024 My preferred sport is football
Thu Jan  4 14:07:26 UTC 2024 My preferred sport is football

Відредагуйте ConfigMap:

kubectl edit configmap sport

В редакторі, що з’явиться, змініть значення ключа sport з football на cricket. Збережіть зміни. Інструмент kubectl відповідним чином оновлює ConfigMap (якщо виникне помилка, спробуйте ще раз).

Ось приклад того, як може виглядати маніфест після редагування:

apiVersion: v1
data:
  sport: cricket
kind: ConfigMap
# Наявні метадані можна залишити без змін.
# Значення, які ви побачите, не будуть точно відповідати цим.
metadata:
  creationTimestamp: "2024-01-04T14:05:06Z"
  name: sport
  namespace: default
  resourceVersion: "1743935"
  uid: 024ee001-fe72-487e-872e-34d6464a8a23

Ви повинні побачити наступний вивід:

configmap/sport edited

Відстежуйте (слідкуйте за останніми записами в) логах одного з Pod, що належить до цього Deployment:

kubectl logs deployments/configmap-volume --follow

Через кілька секунд ви повинні побачити зміну виводу логів:

Thu Jan  4 14:11:36 UTC 2024 My preferred sport is football
Thu Jan  4 14:11:46 UTC 2024 My preferred sport is football
Thu Jan  4 14:11:56 UTC 2024 My preferred sport is football
Thu Jan  4 14:12:06 UTC 2024 My preferred sport is cricket
Thu Jan  4 14:12:16 UTC 2024 My preferred sport is cricket

Коли у вас є ConfigMap, змонтований у працюючий Pod, використовуючи або том configMap, або projected том, і ви оновлюєте цей ConfigMap, працюючий Pod майже миттєво бачить це оновлення. Однак ваш застосунок бачить зміни лише в тому випадку, якщо він запрограмований опитувати зміни або стежити за оновленнями файлів. Застосунок, що завантажує свою конфігурацію лише під час запуску, не помітить змін.

Оновлення змінних середовища Pod за допомогою ConfigMap

Використовуйте команду kubectl create configmap для створення ConfigMap з літеральних значень:

kubectl create configmap fruits --from-literal=fruits=apples

Нижче наведено приклад маніфесту Deployment з налаштуванням змінної середовища через ConfigMap fruits.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: configmap-env-var
  labels:
    app.kubernetes.io/name: configmap-env-var
spec:
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/name: configmap-env-var
  template:
    metadata:
      labels:
        app.kubernetes.io/name: configmap-env-var
    spec:
      containers:
        - name: alpine
          image: alpine:3
          env:
            - name: FRUITS
              valueFrom:
                configMapKeyRef:
                  key: fruits
                  name: fruits
          command:
            - /bin/sh
            - -c
            - while true; do echo "$(date) The basket is full of $FRUITS";
                sleep 10; done;
          ports:
            - containerPort: 80

Створіть Deployment:

kubectl apply -f https://k8s.io/examples/deployments/deployment-with-configmap-as-envvar.yaml

Перевірте Pod цього Deployment, щоб переконатися, що вони готові (збігаються з селектором):

kubectl get pods --selector=app.kubernetes.io/name=configmap-env-var

Ви повинні побачити подібний вивід:

NAME                                 READY   STATUS    RESTARTS   AGE
configmap-env-var-59cfc64f7d-74d7z   1/1     Running   0          46s
configmap-env-var-59cfc64f7d-c4wmj   1/1     Running   0          46s
configmap-env-var-59cfc64f7d-dpr98   1/1     Running   0          46s

Ключ-значення у ConfigMap налаштовано як змінну середовища в контейнері Pod. Перевірте це, переглянувши логи одного Pod, що належить до Deployment.

kubectl logs deployment/configmap-env-var

Ви повинні побачити подібний вивід:

Found 3 pods, using pod/configmap-env-var-7c994f7769-l74nq
Thu Jan  4 16:07:06 UTC 2024 The basket is full of apples
Thu Jan  4 16:07:16 UTC 2024 The basket is full of apples
Thu Jan  4 16:07:26 UTC 2024 The basket is full of apples

Відредагуйте ConfigMap:

kubectl edit configmap fruits

В редакторі, що зʼявиться, змініть значення ключа fruits з apples на mangoes. Збережіть зміни. Інструмент kubectl відповідним чином оновлює ConfigMap (якщо виникне помилка, спробуйте ще раз).

Ось приклад того, як може виглядати маніфест після редагування:

apiVersion: v1
data:
  fruits: mangoes
kind: ConfigMap
# Наявні метадані можна залишити без змін.
# Значення, які ви побачите, не будуть точно відповідати цим.
metadata:
  creationTimestamp: "2024-01-04T16:04:19Z"
  name: fruits
  namespace: default
  resourceVersion: "1749472"

Ви повинні побачити наступний вивід:

configmap/fruits edited

Відстежуйте логи Deployment та спостерігайте за виводом протягом кількох секунд:

# Як пояснюється у тексті, вивід НЕ змінюється
kubectl logs deployments/configmap-env-var --follow

Зверніть увагу, що вивід залишається незмінним, навіть якщо ви редагували ConfigMap:

Thu Jan  4 16:12:56 UTC 2024 The basket is full of apples
Thu Jan  4 16:13:06 UTC 2024 The basket is full of apples
Thu Jan  4 16:13:16 UTC 2024 The basket is full of apples
Thu Jan  4 16:13:26 UTC 2024 The basket is full of apples

Ви можете ініціювати таку заміну. Виконайте розгортання для Deployment, використовуючи kubectl rollout:

# Запустіть розгортання
kubectl rollout restart deployment configmap-env-var

# Дочекайтеся завершення розгортання
kubectl rollout status deployment configmap-env-var --watch=true

Далі перевірте Deployment:

kubectl get deployment configmap-env-var

Ви повинні побачити подібний вивід:

NAME                READY   UP-TO-DATE   AVAILABLE   AGE
configmap-env-var   3/3     3            3           12m

Перевірте Podʼи:

kubectl get pods --selector=app.kubernetes.io/name=configmap-env-var

Розгортання змушує Kubernetes створити новий ReplicaSet для Deployment; це означає, що наявні Podʼи з часом завершуються, а нові створюються. Через кілька секунд ви повинні побачити подібний вивід:

NAME                                 READY   STATUS        RESTARTS   AGE
configmap-env-var-6d94d89bf5-2ph2l   1/1     Running       0          13s
configmap-env-var-6d94d89bf5-74twx   1/1     Running       0          8s
configmap-env-var-6d94d89bf5-d5vx8   1/1     Running       0          11s

Перегляньте логи для одного з Podʼів у цьому Deployment:

# Виберіть один Pod, що належить до Deployment, і перегляньте його логи
kubectl logs deployment/configmap-env-var

Ви повинні побачити подібний вивід:

Found 3 pods, using pod/configmap-env-var-6d9ff89fb6-bzcf6
Thu Jan  4 16:30:35 UTC 2024 The basket is full of mangoes
Thu Jan  4 16:30:45 UTC 2024 The basket is full of mangoes
Thu Jan  4 16:30:55 UTC 2024 The basket is full of mangoes

Це демонструє сценарій оновлення змінних середовища у Podʼі, що отримані з ConfigMap. Зміни до значень ConfigMap застосовуються до Pod під час наступного розгортання. Якщо Pod створюються з іншої причини, наприклад, при масштабуванні Deployment, тоді нові Pod також використовують останні значення конфігурації; якщо ви не ініціюєте розгортання, то ви можете виявити, що ваш застосунок працює зі змішаними старими та новими значеннями змінних середовища.

Оновлення конфігурації через ConfigMap у багатоконтейнерному Поді

Використовуйте команду kubectl create configmap, щоб створити ConfigMap з літеральних значень:

kubectl create configmap color --from-literal=color=red

Нижче подано приклад маніфесту для Deployment, що керує набором Podʼів, кожен з двома контейнерами. Два контейнери спільно використовують том emptyDir, який вони використовують для звʼязку. Перший контейнер працює як вебсервер (nginx). Шлях монтування для спільного тому в контейнері вебсервера — /usr/share/nginx/html. Другий допоміжний контейнер базується на alpine, і для цього контейнера том emptyDir монтується у /pod-data. Допоміжний контейнер записує файл у HTML, чий вміст залежить від ConfigMap. Контейнер вебсервера обслуговує HTML за допомогою HTTP.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: configmap-two-containers
  labels:
    app.kubernetes.io/name: configmap-two-containers
spec:
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/name: configmap-two-containers
  template:
    metadata:
      labels:
        app.kubernetes.io/name: configmap-two-containers
    spec:
      volumes:
        - name: shared-data
          emptyDir: {}
        - name: config-volume
          configMap:
            name: color
      containers:
        - name: nginx
          image: nginx
          volumeMounts:
            - name: shared-data
              mountPath: /usr/share/nginx/html
        - name: alpine
          image: alpine:3
          volumeMounts:
            - name: shared-data
              mountPath: /pod-data
            - name: config-volume
              mountPath: /etc/config
          command:
            - /bin/sh
            - -c
            - while true; do echo "$(date) My preferred color is $(cat /etc/config/color)" > /pod-data/index.html;
              sleep 10; done;

Створіть Deployment:

kubectl apply -f https://k8s.io/examples/deployments/deployment-with-configmap-two-containers.yaml

Перевірте Podʼи для цього Deployment, щоб переконатися, що вони готові (збігаються з селектором):

kubectl get pods --selector=app.kubernetes.io/name=configmap-two-containers

Ви повинні побачити вивід, схожий на:

NAME                                        READY   STATUS    RESTARTS   AGE
configmap-two-containers-565fb6d4f4-2xhxf   2/2     Running   0          20s
configmap-two-containers-565fb6d4f4-g5v4j   2/2     Running   0          20s
configmap-two-containers-565fb6d4f4-mzsmf   2/2     Running   0          20s

Надайте доступ до Deployment (інструмент kubectl створює Service для вас):

kubectl expose deployment configmap-two-containers --name=configmap-service --port=8080 --target-port=80

Використайте kubectl, щоб перенаправити порт:

kubectl port-forward service/configmap-service 8080:8080 & # це залишається запущеним у фоновому режимі

Отримайте доступ до Service.

curl http://localhost:8080

Ви повинні побачити вивід, схожий на:

Fri Jan  5 08:08:22 UTC 2024 My preferred color is red

Відредагуйте ConfigMap:

kubectl edit configmap color

У відкритому редакторі, змініть значення ключа color з red на blue. Збережіть свої зміни. Інструмент kubectl оновлює ConfigMap відповідно (якщо ви побачите помилку, спробуйте ще раз).

Ось приклад того, як може виглядати цей маніфест після редагування:

apiVersion: v1
data:
  color: blue
kind: ConfigMap
# Ви можете залишити наявні метадані такими, які вони є.
# Значення, які ви побачите, не збігатимуться з цими.
metadata:
  creationTimestamp: "2024-01-05T08:12:05Z"
  name: color
  namespace: configmap
  resourceVersion: "1801272"
  uid: 80d33e4a-cbb4-4bc9-ba8c-544c68e425d6

Затримайтесь на URL сервісу протягом кількох секунд.

# Скасуйте це, коли будете задоволені (Ctrl-C)
while true; do curl --connect-timeout 7.5 http://localhost:8080; sleep 10; done

Ви повинні побачити, що виведення змінюється наступним чином:

Fri Jan  5 08:14:00 UTC 2024 My preferred color is red
Fri Jan  5 08:14:02 UTC 2024 My preferred color is red
Fri Jan  5 08:14:20 UTC 2024 My preferred color is red
Fri Jan  5 08:14:22 UTC 2024 My preferred color is red
Fri Jan  5 08:14:32 UTC 2024 My preferred color is blue
Fri Jan  5 08:14:43 UTC 2024 My preferred color is blue
Fri Jan  5 08:15:00 UTC 2024 My preferred color is blue

Оновлення конфігурації через ConfigMap у Поді з контейнером sidecar

Вищевказаний сценарій можна відтворити за допомогою контейнера sidecar як допоміжного контейнера для запису HTML-файлу. Оскільки контейнер sidecar концептуально є контейнером ініціалізації, гарантується, що він запускається перед головним контейнером вебсервера. Це забезпечує, що файл HTML завжди доступний, коли вебсервер буде готовий його обслуговувати. Будь ласка, дивіться Увімкнення контейнерів sidecar, щоб скористатися цією функцією.

Якщо ви продовжуєте з попереднього сценарію, ви можете використати знову ConfigMap з імʼям color для цього сценарію. Якщо ви виконуєте цей сценарій самостійно, використовуйте команду kubectl create configmap, щоб створити ConfigMap з літеральних значень:

kubectl create configmap color --from-literal=color=blue

Нижче наведено приклад маніфесту для Deployment, що керує набором Podʼів, кожен з головним контейнером і контейнером sidecar. Обидва контейнери використовують спільний том emptyDir для звʼязку. Головний контейнер працює як вебсервер (NGINX). Шлях монтування для спільного тому в контейнері вебсервера — /usr/share/nginx/html. Другий контейнер є контейнером sidecar, який базується на Alpine Linux і діє як допоміжний контейнер. Для цього контейнера том emptyDir монтується у /pod-data. Контейнер sidecar записує файл у HTML, чий вміст залежить від ConfigMap. Контейнер вебсервера обслуговує HTML через HTTP.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: configmap-sidecar-container
  labels:
    app.kubernetes.io/name: configmap-sidecar-container
spec:
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/name: configmap-sidecar-container
  template:
    metadata:
      labels:
        app.kubernetes.io/name: configmap-sidecar-container
    spec:
      volumes:
        - name: shared-data
          emptyDir: {}
        - name: config-volume
          configMap:
            name: color
      containers:
        - name: nginx
          image: nginx
          volumeMounts:
            - name: shared-data
              mountPath: /usr/share/nginx/html
      initContainers:
        - name: alpine
          image: alpine:3
          restartPolicy: Always
          volumeMounts:
            - name: shared-data
              mountPath: /pod-data
            - name: config-volume
              mountPath: /etc/config
          command:
            - /bin/sh
            - -c
            - while true; do echo "$(date) My preferred color is $(cat /etc/config/color)" > /pod-data/index.html;
              sleep 10; done;

Створіть Deployment:

kubectl apply -f https://k8s.io/examples/deployments/deployment-with-configmap-and-sidecar-container.yaml

Перевірте Podʼи для цього Deployment, щоб переконатися, що вони готові (збігаються з селектором):

kubectl get pods --selector=app.kubernetes.io/name=configmap-sidecar-container

Ви повинні побачити вивід, схожий на:

NAME                                           READY   STATUS    RESTARTS   AGE
configmap-sidecar-container-5fb59f558b-87rp7   2/2     Running   0          94s
configmap-sidecar-container-5fb59f558b-ccs7s   2/2     Running   0          94s
configmap-sidecar-container-5fb59f558b-wnmgk   2/2     Running   0          94s

Надайте доступ до Deployment (інструмент kubectl створює Service для вас):

kubectl expose deployment configmap-sidecar-container --name=configmap-sidecar-service --port=8081 --target-port=80

Використайте kubectl, щоб перенаправити порт:

kubectl port-forward service/configmap-sidecar-service 8081:8081 & # це залишається запущеним у фоновому режимі

Отримайте доступ до Service.

curl http://localhost:8081

Ви повинні побачити вивід, схожий на:

Sat Feb 17 13:09:05 UTC 2024 My preferred color is blue

Відредагуйте ConfigMap:

kubectl edit configmap color

У відкритому редакторі змініть значення ключа color з blue на green. Збережіть свої зміни. Інструмент kubectl оновлює ConfigMap відповідно (якщо ви побачите помилку, спробуйте ще раз).

Ось приклад того, як може виглядати цей маніфест після редагування:

apiVersion: v1
data:
  color: green
kind: ConfigMap
# You can leave the existing metadata as they are.
# The values you'll see won't exactly match these.
metadata:
  creationTimestamp: "2024-02-17T12:20:30Z"
  name: color
  namespace: default
  resourceVersion: "1054"
  uid: e40bb34c-58df-4280-8bea-6ed16edccfaa

Затримайтесь на URL сервісу протягом кількох секунд.

# Скасуйте це, коли будете задоволені (Ctrl-C)
while true; do curl --connect-timeout 7.5 http://localhost:8081; sleep 10; done

Ви повинні побачити, що вивід змінюється наступним чином:

Sat Feb 17 13:12:35 UTC 2024 My preferred color is blue
Sat Feb 17 13:12:45 UTC 2024 My preferred color is blue
Sat Feb 17 13:12:55 UTC 2024 My preferred color is blue
Sat Feb 17 13:13:05 UTC 2024 My preferred color is blue
Sat Feb 17 13:13:15 UTC 2024 My preferred color is green
Sat Feb 17 13:13:25 UTC 2024 My preferred color is green
Sat Feb 17 13:13:35 UTC 2024 My preferred color is green

Оновлення конфігурації через незмінний ConfigMap, який монтується як том

Нижче наведено приклад маніфесту для незмінного ConfigMap.

apiVersion: v1
data:
  company_name: "ACME, Inc." # наявна вигадана назва компанії
kind: ConfigMap
immutable: true
metadata:
  name: company-name-20150801

Створіть незмінний ConfigMap:

kubectl apply -f https://k8s.io/examples/configmap/immutable-configmap.yaml

Нижче наведено приклад маніфесту Deployment з незмінним ConfigMap company-name-20150801, який монтується як том в єдиний контейнер Pod.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: immutable-configmap-volume
  labels:
    app.kubernetes.io/name: immutable-configmap-volume
spec:
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/name: immutable-configmap-volume
  template:
    metadata:
      labels:
        app.kubernetes.io/name: immutable-configmap-volume
    spec:
      containers:
        - name: alpine
          image: alpine:3
          command:
            - /bin/sh
            - -c
            - while true; do echo "$(date) The name of the company is $(cat /etc/config/company_name)";
              sleep 10; done;
          ports:
            - containerPort: 80
          volumeMounts:
            - name: config-volume
              mountPath: /etc/config
      volumes:
        - name: config-volume
          configMap:
            name: company-name-20150801

Створіть Deployment:

kubectl apply -f https://k8s.io/examples/deployments/deployment-with-immutable-configmap-as-volume.yaml

Перевірте Podʼи для цього Deployment, щоб переконатися, що вони готові (збігаються з селектором):

kubectl get pods --selector=app.kubernetes.io/name=immutable-configmap-volume

Ви повинні побачити вивід схожий на:

ІNAME                                          READY   STATUS    RESTARTS   AGE
immutable-configmap-volume-78b6fbff95-5gsfh   1/1     Running   0          62s
immutable-configmap-volume-78b6fbff95-7vcj4   1/1     Running   0          62s
immutable-configmap-volume-78b6fbff95-vdslm   1/1     Running   0          62s

Контейнер Podʼа посилається на дані, визначені в ConfigMap, і використовує їх для виводу звіту в stdout. Ви можете перевірити цей звіт, переглянувши логи для одного з Podʼів у цьому Deployment:

# Виберіть один Pod, який належить до Deployment, і перегляньте його логи
kubectl logs deployments/immutable-configmap-volume

Ви повинні побачити вивід, подібний до:

Found 3 pods, using pod/immutable-configmap-volume-78b6fbff95-5gsfh
Wed Mar 20 03:52:34 UTC 2024 The name of the company is ACME, Inc.
Wed Mar 20 03:52:44 UTC 2024 The name of the company is ACME, Inc.
Wed Mar 20 03:52:54 UTC 2024 The name of the company is ACME, Inc.

Створіть новий незмінний ConfigMap, використовуючи наведений нижче маніфест:

apiVersion: v1
data:
  company_name: "Fiktivesunternehmen GmbH" # нова вигадана назва компанії
kind: ConfigMap
immutable: true
metadata:
  name: company-name-20240312
kubectl apply -f https://k8s.io/examples/configmap/new-immutable-configmap.yaml

Ви повинні побачити вивід схожий на:

configmap/company-name-20240312 створено

Перевірте новостворений ConfigMap:

kubectl get configmap

Ви повинні побачити вивід, який відображає обидва? старий та новий ConfigMaps:

NAME                    DATA   AGE
company-name-20150801   1      22m
company-name-20240312   1      24s

Відредагуйте Deployment, щоб вказати новий ConfigMap

Редагування Deployment:

kubectl edit deployment immutable-configmap-volume

В редакторі, що зʼявиться, оновіть наявне визначення тому для використання нового ConfigMap.

volumes:
- configMap:
    defaultMode: 420
    name: company-name-20240312 # Оновіть це поле
  name: config-volume

Ви маєте побачити вивід, подібний до:

deployment.apps/immutable-configmap-volume edited

Це запустить розгортання. Почекайте поки всі раніше створені Podʼи завершаться, а нові Podʼи будуть в стані ready.

Відстежуйте стан Podʼів:

kubectl get pods --selector=app.kubernetes.io/name=immutable-configmap-volume
NAME                                          READY   STATUS        RESTARTS   AGE
immutable-configmap-volume-5fdb88fcc8-29v8n   1/1     Running       0          13s
immutable-configmap-volume-5fdb88fcc8-52ddd   1/1     Running       0          14s
immutable-configmap-volume-5fdb88fcc8-n5jx4   1/1     Running       0          15s
immutable-configmap-volume-78b6fbff95-5gsfh   1/1     Terminating   0          32m
immutable-configmap-volume-78b6fbff95-7vcj4   1/1     Terminating   0          32m
immutable-configmap-volume-78b6fbff95-vdslm   1/1     Terminating   0          32m

Ви нарешті побачите вивід, подібний до:

NAME                                          READY   STATUS    RESTARTS   AGE
immutable-configmap-volume-5fdb88fcc8-29v8n   1/1     Running   0          43s
immutable-configmap-volume-5fdb88fcc8-52ddd   1/1     Running   0          44s
immutable-configmap-volume-5fdb88fcc8-n5jx4   1/1     Running   0          45s

Перевірте логи для одного з Podʼів у цьому Deployment:

# Виберіть один Pod, який належить до Deployment, і перегляньте його логи
kubectl logs deployments/immutable-configmap-volume

Ви повинні побачити вивід, подібний до:

Found 3 pods, using pod/immutable-configmap-volume-5fdb88fcc8-n5jx4
Wed Mar 20 04:24:17 UTC 2024 The name of the company is Fiktivesunternehmen GmbH
Wed Mar 20 04:24:27 UTC 2024 The name of the company is Fiktivesunternehmen GmbH
Wed Mar 20 04:24:37 UTC 2024 The name of the company is Fiktivesunternehmen GmbH

Як тільки всі розгортання мігрують на використання нового незмінного ConfigMap, потрібно видалити старий ConfigMap:

kubectl delete configmap company-name-20150801

Підсумок

Зміни у ConfigMap, змонтованому як том у Pod, стають доступними безперешкодно після наступної синхронізації kubelet.

Зміни у ConfigMap, який налаштовує змінні середовища для Pod, стають доступними після наступного розгортання для цього Pod.

Після того як ConfigMap позначено як незмінний, неможливо скасувати цю зміну (ви не можете зробити незмінний ConfigMap змінним), і ви також не можете внести жодну зміну до вмісту поля data або binaryData. Ви можете видалити та створити ConfigMap заново, або можете створити новий відмінний ConfigMap. Коли ви видаляєте ConfigMap, запущені контейнери та їхні Podʼи зберігають точку монтування до будь-якого тому, який посилається на цей наявнийConfigMap.

Очищення

Завершіть команди kubectl port-forward, якщо вони виконуються.

Видаліть ресурси, створені під час навчання:

kubectl delete deployment configmap-volume configmap-env-var configmap-two-containers configmap-sidecar-container immutable-configmap-volume
kubectl delete service configmap-service configmap-sidecar-service
kubectl delete configmap sport fruits color company-name-20240312

kubectl delete configmap company-name-20150801 # У випадку, якщо він не був оброблений під час виконання завдання

3.2 - Конфігурування Redis за допомогою ConfigMap

Ця сторінка надає реальний приклад конфігурування Redis за допомогою ConfigMap і базується на завданні Конфігурування Pod для використання ConfigMap.

Цілі

  • Створити ConfigMap з конфігураційними значеннями Redis
  • Створити Pod з Redis, який монтує та використовує створений ConfigMap
  • Перевірити, що конфігурація була правильно застосована.

Перш ніж ви розпочнете

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Для перевірки версії введіть kubectl version.

Реальний приклад: Конфігурування Redis за допомогою ConfigMap

Виконайте наведені нижче кроки для конфігурування кешу Redis за допомогою даних, збережених у ConfigMap.

Спершу створіть ConfigMap з порожнім блоком конфігурації:

cat <<EOF >./example-redis-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: example-redis-config
data:
  redis-config: ""
EOF

Застосуйте створений вище ConfigMap разом з маніфестом Podʼа Redis:

kubectl apply -f example-redis-config.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/pods/config/redis-pod.yaml

Перегляньте вміст маніфесту Podʼа Redis і зверніть увагу на наступне:

  • Том config створено за допомогою spec.volumes[1]
  • Пара key і path у spec.volumes[1].configMap.items[0] експонує ключ redis-config з example-redis-config ConfigMap як файл з назвою redis.conf у томі config.
  • Том config потім монтується в /redis-master за допомогою spec.containers[0].volumeMounts[1].

Це має загальний ефект експозиції даних з data.redis-config з example-redis-config ConfigMap як /redis-master/redis.conf всередині Pod.

apiVersion: v1
kind: Pod
metadata:
  name: redis
spec:
  containers:
  - name: redis
    image: redis:5.0.4
    command:
      - redis-server
      - "/redis-master/redis.conf"
    env:
    - name: MASTER
      value: "true"
    ports:
    - containerPort: 6379
    resources:
      limits:
        cpu: "0.1"
    volumeMounts:
    - mountPath: /redis-master-data
      name: data
    - mountPath: /redis-master
      name: config
  volumes:
    - name: data
      emptyDir: {}
    - name: config
      configMap:
        name: example-redis-config
        items:
        - key: redis-config
          path: redis.conf

Перегляньте створені обʼєкти:

kubectl get pod/redis configmap/example-redis-config 

Ви повинні побачити наступний вивід:

NAME        READY   STATUS    RESTARTS   AGE
pod/redis   1/1     Running   0          8s

NAME                             DATA   AGE
configmap/example-redis-config   1      14s

Нагадаємо, що ми залишили ключ redis-config у example-redis-config ConfigMap порожнім:

kubectl describe configmap/example-redis-config

Ви повинні побачити порожній ключ redis-config:

Name:         example-redis-config
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
redis-config:

Використовуйте kubectl exec, щоб увійти в Pod і запустити інструмент redis-cli, щоб перевірити поточну конфігурацію:

kubectl exec -it redis -- redis-cli

Перевірте maxmemory:

127.0.0.1:6379> CONFIG GET maxmemory

Він має показати типове значення 0:

1) "maxmemory"
2) "0"

Аналогічно, перевірте maxmemory-policy:

127.0.0.1:6379> CONFIG GET maxmemory-policy

Що також повинно показати типове значення noeviction:

1) "maxmemory-policy"
2) "noeviction"

Тепер додамо деякі конфігураційні значення до example-redis-config ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: example-redis-config
data:
  redis-config: |
    maxmemory 2mb
    maxmemory-policy allkeys-lru    

Застосуйте оновлений ConfigMap:

kubectl apply -f example-redis-config.yaml

Перевірте, що ConfigMap був оновлений:

kubectl describe configmap/example-redis-config

Ви повинні побачити конфігураційні значення, які ми щойно додали:

Name:         example-redis-config
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
redis-config:
----
maxmemory 2mb
maxmemory-policy allkeys-lru

Ще раз перевірте Pod Redis за допомогою redis-cli через kubectl exec, щоб побачити, чи конфігурація була застосована:

kubectl exec -it redis -- redis-cli

Перевірте maxmemory:

127.0.0.1:6379> CONFIG GET maxmemory

Він залишається з типовим значенням 0:

1) "maxmemory"
2) "0"

Аналогічно, maxmemory-policy залишається з типовими налаштуваннями noeviction:

127.0.0.1:6379> CONFIG GET maxmemory-policy

Повертає:

1) "maxmemory-policy"
2) "noeviction"

Конфігураційні значення не змінилися, оскільки Pod необхідно перезапустити, щоб отримати оновлені значення з асоційованих ConfigMap. Видалимо та заново створимо Pod:

kubectl delete pod redis
kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/pods/config/redis-pod.yaml

Тепер ще раз перевірте конфігураційні значення:

kubectl exec -it redis -- redis-cli

Перевірте maxmemory:

127.0.0.1:6379> CONFIG GET maxmemory

Тепер він має показати оновлене значення 2097152:

1) "maxmemory"
2) "2097152"

Аналогічно, maxmemory-policy також було оновлено:

127.0.0.1:6379> CONFIG GET maxmemory-policy

Він тепер показує бажане значення allkeys-lru:

1) "maxmemory-policy"
2) "allkeys-lru"

Очистіть свою роботу, видаливши створені ресурси:

kubectl delete pod/redis configmap/example-redis-config

Що далі

3.3 - Використання контейнерів sidecar

Цей розділ актуальний для людей, які впроваджують нову вбудовану функцію sidecar-контейнерів для своїх навантажень.

Sidecar-контейнери — це не нова концепція, про що було згадано в блог-пості ще у 2015 році. Kubernetes дозволяв запускати декілька контейнерів у Pod, щоб реалізувати цю концепцію. Однак запуск sidecar-контейнера як звичайного контейнера має багато обмежень, які вирішуються за допомогою нової підтримки вбудованих sidecar-контейнерів.

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.29 [beta] (стандартно увімкнено: true)

Цілі

  • Зрозуміти необхідність використання sidecar-контейнерів.
  • Вміти розвʼязувати проблеми з sidecar-контейнерами.
  • Зрозуміти варіанти універсального "впровадження" sidecar-контейнерів у будь-яке навантаження.

Перш ніж ви розпочнете

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Версія вашого Kubernetes сервера має бути не старішою ніж 1.29. Для перевірки версії введіть kubectl version.

Огляд sidecar-контейнерів

Sidecar-контейнери — це додаткові контейнери, які працюють разом з основним контейнером застосунку в межах одного Podʼа. Ці контейнери використовуються для покращення або розширення функціональності основного app-контейнера, надаючи додаткові послуги або функціональність, такі як логування, моніторинг, безпека або синхронізація даних, без прямого втручання в код основного застосунку. Детальніше можна прочитати на сторінці концепції Sidecar-контейнерів.

Концепція sidecar-контейнерів не є новою, і є багато її реалізацій. Як і sidecar-контейнери, які ви, як особа, що визначає Pod, хочете запустити, ви також можете побачити, що деякі надбудови змінюють Podʼи, до того, як Podʼи почнуть працювати, додаючи додаткові sidecar-контейнери. Механізми інʼєкцій цих додаткових sidecar-контейнерів часто використовують мутуючі вебхуки. Наприклад, надбудова service mesh може впроваджувати sidecar-контейнер, який налаштовує взаємний TLS і шифрування під час передачі між різними Podʼами.

Хоча концепція sidecar-контейнерів не є новою, вбудована реалізація цієї функції в Kubernetes, однак, нова. І як і з будь-якою новою функцією, впровадження цієї функції може викликати певні труднощі.

Цей навчальний посібник досліджує труднощі та рішення, з якими можуть зіткнутися кінцеві користувачі, а також автори sidecar-контейнерів.

Переваги вбудованих sidecar-контейнерів

Використання нативної підтримки sidecar-контейнерів у Kubernetes має кілька переваг:

  1. Ви можете налаштувати нативний sidecar-контейнер так, щоб він запускався перед init-контейнерами.
  2. Вбудовані sidecar-контейнери можна налаштувати так, щоб вони гарантовано завершувалися останніми. Sidecar-контейнери завершують роботу за допомогою сигналу SIGTERM, коли всі звичайні контейнери завершили роботу та завершилися. Якщо sidecar-контейнер не завершує роботу коректно, сигнал SIGKILL буде використано для його завершення.
  3. Для Jobs, коли restartPolicy: OnFailure або restartPolicy: Never, нативні sidecar-контейнери не блокують завершення Pod. Для старих sidecar-контейнерів потрібно було приділяти особливу увагу вирішенню цієї ситуації.
  4. Також для Jobs, вбудовані sidecar-контейнери будуть продовжувати перезапускатися після завершення, навіть якщо звичайні контейнери не будуть цього робити при restartPolicy: Never у Pod.

Дивіться відмінності від контейнерів ініціалізації для додаткових відомостей.

Впровадження вбудованих sidecar-контейнерів

Функціональна можливість SidecarContainers перебуває у стані бета-версії, починаючи з версії Kubernetes 1.29, і є стандартно увімкненою. Деякі кластери можуть мати цю функцію вимкненою або мати встановлене програмне забезпечення, яке не сумісне з цією функцією.

Коли це трапляється, Pod може бути відхилено або sidecar-контейнери можуть блокувати запуск Podʼа, роблячи Pod непридатним для використання. Цей стан легко виявити, оскільки Pod просто застряє на стадії ініціалізації. Однак рідко буває зрозуміло, що спричинило проблему.

Ось міркування та кроки з усунення несправностей, які можна виконати під час впровадження sidecar-контейнерів для свого навантаження.

Переконайтеся, що функціональну можливість увімкнено

Перш за все, переконайтеся, що як API-сервер, так і вузли мають версію Kubernetes v1.29 або пізнішу. Функція не працюватиме на кластерах, де вузли працюють на більш ранніх версіях, де вона не увімкнена.

Ви повинні переконатися, що функціональна можливість увімкнено як для API-сервера (серверів) у межах панелі управління, так і для всіх вузлів.

Одним зі способів перевірити увімкнення функціональної можливості є виконання команди:

  • Для API-сервера

    kubectl get --raw /metrics | grep kubernetes_feature_enabled | grep SidecarContainers
    
  • Для окремого вузла

    kubectl get --raw /api/v1/nodes/<node-name>/proxy/metrics | grep kubernetes_feature_enabled | grep SidecarContainers
    

Якщо ви бачите щось на кшталт:

kubernetes_feature_enabled{name="SidecarContainers",stage="BETA"} 1

це означає, що функцію увімкнено.

Перевірка сторонніх інструментів і мутуючих вебхуків

Якщо у вас виникли проблеми під час перевірки функції, це може бути ознакою того, що один зі сторонніх інструментів або мутуючих вебхуків не працюють.

Коли функціональну можливість SidecarContainers увімкнено, Podʼи отримують нове поле в їхньому API. Деякі інструменти або мутуючі вебхуки могли бути створені на основі попередньої версії Kubernetes API.

Якщо інструменти передають невідомі поля як є, використовуючи різні стратегії виправлення для змінни об’єкта Pod, це не буде проблемою. Однак є інструменти, які видалять невідомі поля; якщо у вас є такі, їх потрібно буде перекомпілювати з v1.28+ версією клієнтського коду Kubernetes API.

Спосіб перевірки цього полягає у використанні команди kubectl describe pod для вашого Podʼа, який пройшов через мутуючий admission webhook. Якщо будь-які інструменти вилучили нове поле (restartPolicy: Always), ви не побачите його у виводі команди.

Якщо ви зіткнулися з такою проблемою, рекомендується повідомити автора інструментів або вебхуків, щоб вони використовували одну зі стратегій накладання патчів для модифікації обʼєктів замість їх повного оновлення.

Автоматичне встромляння sidecar-контейнерів

Якщо ви використовуєте програмне забезпечення, яке автоматично встромляє sidecar-контейнери, існує кілька можливих стратегій, які ви можете застосувати, щоб забезпечити можливість використання нативних sidecar-контейнерів. Усі ці стратегії загалом є варіантами вибору того, чи буде Pod, до якого встромляється sidecar, розміщений на вузлі, який підтримує цю функцію.

Наприклад, можна ознайомитися з цією дискусією в спільноті Istio, яка досліджує наведені нижче варіанти.

  1. Позначення Podʼів, що розміщуються на вузлах із підтримкою sidecars. Ви можете використовувати мітки вузлів і node affinity, щоб позначити вузли, які підтримують sidecar-контейнери, і забезпечити розміщення Pods на цих вузлах.
  2. Перевірка сумісності вузлів під час додавання контейнера. Під час впровадження sidecar-контейнерів можна використовувати такі стратегії перевірки сумісності вузлів:
    • Запитати версію вузла та припустити, що функціональну можливість увімкнено для версії 1.29+.
    • Запитати метрики Prometheus вузла та перевірити стан увімкнення функції.
    • Припустити, що вузли працюють із підтримуваною версійною розбіжністю від API-сервера.
    • Можуть існувати інші власні способи визначення сумісності вузлів.
  3. Розробка універсального інжектора sidecar-контейнерів. Ідея універсального sidecar-контейнера полягає в тому, щоб додати sidecar-контейнер як звичайний контейнер, а також як нативний sidecar-контейнер, і мати логіку на рівні виконання, яка визначить, який варіант працюватиме. Універсальний інжектор sidecar є ресурсозатратним, оскільки він враховуватиме запити двічі, але його можна розглядати як робоче рішення для особливих випадків.
    • Один зі способів полягає в тому, щоб під час запуску нативного sidecar-контейнера визначити версію вузла та завершити роботу негайно, якщо версія не підтримує функцію sidecar.
    • Розгляньте дизайн із виявленням функції під час виконання:
      • Визначте empty dir, щоб контейнери могли взаємодіяти один з одним.
      • Впровадьте init-контейнер, який назвемо NativeSidecar, з restartPolicy=Always.
      • NativeSidecar має записати файл в empty dir під час першого запуску та завершити роботу з кодом виходу 0.
      • NativeSidecar під час повторного запуску (коли нативні sidecar підтримуються) перевіряє, чи файл уже існує в empty dir, і змінює його, вказуючи, що вбудовані sidecar-контейнери підтримуються і працюють.
      • Впровадьте звичайний контейнер, який назвемо OldWaySidecar.
      • OldWaySidecar під час запуску перевіряє наявність файлу в empty dir.
      • Якщо файл вказує, що NativeSidecar не працює, він припускає, що функція sidecar не підтримується, і працює як sidecar.
      • Якщо файл вказує, що NativeSidecar працює, він або не робить нічого та спить назавжди (у випадку, коли Pod має restartPolicy=Always), або завершить роботу негайно з кодом виходу 0 (у випадку, коли Pod має restartPolicy!=Always).

Що далі

4 - Безпека

Безпека є важливою темою для більшості організацій та людей, які використовують кластери Kubernetes. Ви можете знайти базовий список перевірок безпеки в іншому місці документації Kubernetes.

Щоб дізнатися, як розгорнути та керувати аспектами безпеки Kubernetes, ви можете виконати практичні завдання в цьому розділі.

4.1 - Застосування стандартів безпеки Podʼів на рівні кластера

Безпека Pod покладається на контролер допуску, що виконує перевірки відповідно до Стандартів безпеки Podʼів в Kubernetes при створенні нових Podʼів. Це функція, має загальну доступність з випуску v1.25. Цей навчальний посібник показує, як застосувати стандарт безпеки baseline на рівні кластера, що застосовує стандартну конфігурацію для всіх просторів імен у кластері.

Для застосування стандартів безпеки Podʼів до певних просторів імен дивіться Застосування стандартів безпеки Podʼів на рівні простору імен.

Якщо ви працюєте з версією Kubernetes, відмінною від v1.32, перевірте документацію для вашої версії.

Перш ніж ви розпочнете

Встановіть на ваш компʼютер наступне:

Цей навчальний посібник показує, як ви можете налаштувати кластер Kubernetes, який ви повністю контролюєте. Якщо ви вивчаєте, як налаштувати Pod Security Admission для кластера, що надається постачальником послуг, де ви не можете налаштувати панель управління, прочитайте Застосування стандартів безпеки Podʼів на рівні простору імен.

Виберіть правильний стандарт безпеки Podʼів

Pod Security Admission дозволяє застосовувати вбудовані Стандарти безпеки Podʼів у режимах: enforce, audit та warn.

Щоб зібрати інформацію, яка допоможе вам вибрати стандарти безпеки Podʼів, які найбільш підходять для вашої конфігурації, виконайте наступне:

  1. Створіть кластер, в якому не застосовані стандарти безпеки Podʼів:

    kind create cluster --name psa-wo-cluster-pss
    

    Вивід подібний до:

    Creating cluster "psa-wo-cluster-pss" ...
    ✓ Ensuring node image (kindest/node:v1.32.0) 🖼
    ✓ Preparing nodes 📦
    ✓ Writing configuration 📜
    ✓ Starting control-plane 🕹️
    ✓ Installing CNI 🔌
    ✓ Installing StorageClass 💾
    Set kubectl context to "kind-psa-wo-cluster-pss"
    You can now use your cluster with:
    
    kubectl cluster-info --context kind-psa-wo-cluster-pss
    
    Thanks for using kind! 😊
    
  2. Встановіть контекст kubectl для нового кластера:

    kubectl cluster-info --context kind-psa-wo-cluster-pss
    

    Вивід подібний до цього:

    Kubernetes control plane is running at https://127.0.0.1:61350
    
    CoreDNS is running at https://127.0.0.1:61350/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
    
    To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
    
  3. Отримайте список просторів імен у кластері:

    kubectl get ns
    

    Вивід подібний до цього:

    NAME                 STATUS   AGE
    default              Active   9m30s
    kube-node-lease      Active   9m32s
    kube-public          Active   9m32s
    kube-system          Active   9m32s
    local-path-storage   Active   9m26s
    
  4. Використовуйте --dry-run=server, щоб зрозуміти, що відбувається при застосуванні різних стандартів безпеки Podʼів:

    1. Privileged

      kubectl label --dry-run=server --overwrite ns --all \
      pod-security.kubernetes.io/enforce=privileged
      

      Вивід подібний до:

      namespace/default labeled
      namespace/kube-node-lease labeled
      namespace/kube-public labeled
      namespace/kube-system labeled
      namespace/local-path-storage labeled
      
    2. Baseline

      kubectl label --dry-run=server --overwrite ns --all \
      pod-security.kubernetes.io/enforce=baseline
      

      Вивід подібний до:

      namespace/default labeled
      namespace/kube-node-lease labeled
      namespace/kube-public labeled
      Warning: existing pods in namespace "kube-system" violate the new PodSecurity enforce level "baseline:latest"
      Warning: etcd-psa-wo-cluster-pss-control-plane (and 3 other pods): host namespaces, hostPath volumes
      Warning: kindnet-vzj42: non-default capabilities, host namespaces, hostPath volumes
      Warning: kube-proxy-m6hwf: host namespaces, hostPath volumes, privileged
      namespace/kube-system labeled
      namespace/local-path-storage labeled
      
    3. Restricted

      kubectl label --dry-run=server --overwrite ns --all \
      pod-security.kubernetes.io/enforce=restricted
      

      Вивід подібний до:

      namespace/default labeled
      namespace/kube-node-lease labeled
      namespace/kube-public labeled
      Warning: existing pods in namespace "kube-system" violate the new PodSecurity enforce level "restricted:latest"
      Warning: coredns-7bb9c7b568-hsptc (and 1 other pod): unrestricted capabilities, runAsNonRoot != true, seccompProfile
      Warning: etcd-psa-wo-cluster-pss-control-plane (and 3 other pods): host namespaces, hostPath volumes, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true
      Warning: kindnet-vzj42: non-default capabilities, host namespaces, hostPath volumes, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true, seccompProfile
      Warning: kube-proxy-m6hwf: host namespaces, hostPath volumes, privileged, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true, seccompProfile
      namespace/kube-system labeled
      Warning: existing pods in namespace "local-path-storage" violate the new PodSecurity enforce level "restricted:latest"
      Warning: local-path-provisioner-d6d9f7ffc-lw9lh: allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true, seccompProfile
      namespace/local-path-storage labeled
      

З попередніх результатів ви можете помітити, що застосування стандарту безпеки privileged не показує жодних попереджень для жодного простору імен. Однак для стандартів baseline і restricted є попередження, зокрема для простору імен kube-system.

Встановлення режимів, версій та стандартів

У цьому розділі ви застосовуєте наступні Стандарти безпеки Pod до версії latest:

  • Стандарт baseline у режимі enforce.
  • Стандарт restricted у режимах warn та audit.

Стандарт безпеки Pod baseline надає зручний проміжний рівень, який дозволяє тримати список винятків коротким та запобігає відомим підвищенням привілеїв.

Додатково, для запобігання витоку підсистеми kube-system, ви виключите простір імен з застосуванням Стандартів безпеки Pod.

При впровадженні перевірки безпеки Pod у власному середовищі оберіть такі пункти:

  1. Враховуючи рівень ризику, застосований до кластера, строгий Стандарт безпеки Pod, наприклад restricted, може бути кращим вибором.

  2. Виключення простору імен kube-system дозволяє підсистемам працювати з підвищеними привілеями у цьому просторі імен. Для реального використання проєкт Kubernetes наполегливо рекомендує вам застосовувати строгі політики RBAC, які обмежують доступ до kube-system, слідуючи принципу найменших привілеїв. Для впровадження вищезазначених стандартів виконайте наступне:

  3. Створіть файл конфігурації, який може бути використаний Контролером допуску Стандартів безпеки Pod для застосування цих Стандартів безпеки Pod:

    mkdir -p /tmp/pss
    cat <<EOF > /tmp/pss/cluster-level-pss.yaml
    apiVersion: apiserver.config.k8s.io/v1
    kind: AdmissionConfiguration
    plugins:
    - name: PodSecurity
      configuration:
        apiVersion: pod-security.admission.config.k8s.io/v1
        kind: PodSecurityConfiguration
        defaults:
          enforce: "baseline"
          enforce-version: "latest"
          audit: "restricted"
          audit-version: "latest"
          warn: "restricted"
          warn-version: "latest"
        exemptions:
          usernames: []
          runtimeClasses: []
          namespaces: [kube-system]
    EOF
    
  4. Налаштуйте API-сервер для використання цього файлу під час створення кластера:

    cat <<EOF > /tmp/pss/cluster-config.yaml
    kind: Cluster
    apiVersion: kind.x-k8s.io/v1alpha4
    nodes:
    - role: control-plane
      kubeadmConfigPatches:
      - |
        kind: ClusterConfiguration
        apiServer:
            extraArgs:
              admission-control-config-file: /etc/config/cluster-level-pss.yaml
            extraVolumes:
              - name: accf
                hostPath: /etc/config
                mountPath: /etc/config
                readOnly: false
                pathType: "DirectoryOrCreate"
      extraMounts:
      - hostPath: /tmp/pss
        containerPath: /etc/config
        readOnly: false
        selinuxRelabel: false
        propagation: None
    EOF
    
  5. Створіть кластер, який використовує Pod Security Admission для застосування цих Стандартів безпеки Pod:

    kind create cluster --name psa-with-cluster-pss --config /tmp/pss/cluster-config.yaml
    

    Вивід буде подібним до цього:

    Creating cluster "psa-with-cluster-pss" ...
     ✓ Ensuring node image (kindest/node:v1.32.0) 🖼
     ✓ Preparing nodes 📦
     ✓ Writing configuration 📜
     ✓ Starting control-plane 🕹️
     ✓ Installing CNI 🔌
     ✓ Installing StorageClass 💾
    Set kubectl context to "kind-psa-with-cluster-pss"
    You can now use your cluster with:
    
    kubectl cluster-info --context kind-psa-with-cluster-pss
    
    Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂
    
  6. Вкажіт kubectl кластер:

    kubectl cluster-info --context kind-psa-with-cluster-pss
    

    Вивід буде схожий на цей:

    Kubernetes control plane is running at https://127.0.0.1:63855
    CoreDNS is running at https://127.0.0.1:63855/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
    
    To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
    
  7. Створіть Pod у просторі імен default:

    apiVersion: v1
     kind: Pod
     metadata:
       name: nginx
     spec:
       containers:
         - image: nginx
           name: nginx
           ports:
             - containerPort: 80
     
    kubectl apply -f https://k8s.io/examples/security/example-baseline-pod.yaml
    

    Pod запускається звичайно, але вивід містить попередження:

    Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
    pod/nginx created
    

Очищення

Тепер ви можете видалити кластери, які ви створили:

kind delete cluster --name psa-with-cluster-pss
kind delete cluster --name psa-wo-cluster-pss

Що далі

4.2 - Застосування Стандартів безпеки Pod на рівні простору імен

Pod Security Admission — це контролер допуску, який застосовує Стандарти безпеки Pod при створенні Podʼів. Це функція, яка є загально доступною з v1.25. У цьому посібнику ви будете застосовувати Стандарт безпеки Pod baseline, по одному простору імен за раз.

Ви також можете застосовувати Стандарти безпеки Pod до кількох просторів імен одночасно на рівні кластера. Щоб дізнатися більше, перейдіть за посиланням Застосування Стандартів безпеки Pod на рівні кластера.

Перш ніж ви розпочнете

Встановіть на ваш компʼютер наступне:

Створення кластера

  1. Створіть кластер kind наступним чином:

    kind create cluster --name psa-ns-level
    

    Вивід буде подібний до цього:

    Creating cluster "psa-ns-level" ...
     ✓ Ensuring node image (kindest/node:v1.32.0) 🖼 
     ✓ Preparing nodes 📦  
     ✓ Writing configuration 📜 
     ✓ Starting control-plane 🕹️ 
     ✓ Installing CNI 🔌 
     ✓ Installing StorageClass 💾 
    Set kubectl context to "kind-psa-ns-level"
    You can now use your cluster with:
    
    kubectl cluster-info --context kind-psa-ns-level
    
    Not sure what to do next? 😅  Check out https://kind.sigs.k8s.io/docs/user/quick-start/
    
  2. Встановіть контекст kubectl для нового кластера:

    kubectl cluster-info --context kind-psa-ns-level
    

    Вивід буде подібний до цього:

    Kubernetes control plane is running at https://127.0.0.1:50996
    CoreDNS is running at https://127.0.0.1:50996/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
    
    To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
    

Створення простору імен

Створіть новий простір імен з назвою example:

kubectl create ns example

Вивід буде подібний до цього:

namespace/example created

Увімкнення перевірки Стандартів безпеки Pod для цього простору імен

  1. Увімкніть Стандарти безпеки Pod на цьому просторі імен за допомогою підтримуваних міток, вбудованих в Pod Security Admission. На цьому кроці ви налаштуєте перевірку, щоб система попереджувала про Podʼи, які не відповідають останньої версії стандарту безпеки підсистеми baseline.

    kubectl label --overwrite ns example \
       pod-security.kubernetes.io/warn=baseline \
       pod-security.kubernetes.io/warn-version=latest
    
  2. Ви можете налаштувати кілька перевірок стандартів безпеки Podʼів для будь-якого простору імен за допомогою міток. Наступна команда буде застосовувати стандарт безпеки Pod baseline, але warn та audit для стандартів безпеки Pod restricted згідно з останньою версією (стандартне значення)

    kubectl label --overwrite ns example \
      pod-security.kubernetes.io/enforce=baseline \
      pod-security.kubernetes.io/enforce-version=latest \
      pod-security.kubernetes.io/warn=restricted \
      pod-security.kubernetes.io/warn-version=latest \
      pod-security.kubernetes.io/audit=restricted \
      pod-security.kubernetes.io/audit-version=latest
    

Перевірте дотримання стандарту безпеки Podʼів

  1. Створіть Pod з базовим стандартом в просторі імен example:

    kubectl apply -n example -f https://k8s.io/examples/security/example-baseline-pod.yaml
    

    Pod дійсно запускається; вивід містить попередження. Наприклад:

    Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
    pod/nginx created
    
  2. Створіть Pod з базовим стандартом у просторі імен default:

    kubectl apply -n default -f https://k8s.io/examples/security/example-baseline-pod.yaml
    

    Вивід буде подібний до такого:

    pod/nginx створено
    

Виконання стандартів безпеки Podʼів та налаштування попереджень було застосовано лише до простору імен example. Ви можете створити такий самий Pod в просторі імен default без будь-яких попереджень.

Очищення

Тепер видаліть кластер, який було створено:

kind delete cluster --name psa-ns-level

Що далі

4.3 - Обмежте доступ контейнера до ресурсів за допомогою AppArmor

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.31 [stable] (стандартно увімкнено: true)

Ця сторінка показує, як завантажити профілі AppArmor на ваших вузлах та забезпечити їх виконання в Podʼах. Щоб дізнатися більше про те, як Kubernetes може обмежувати Podʼи за допомогою AppArmor, див. Обмеження безпеки ядра Linux для Podʼів та контейнерів.

Цілі

  • Ознайомитись з прикладом того, як завантажити профіль на Вузол
  • Дізнайтеся, як забезпечити виконання профілю в Podʼі
  • Дізнайтеся, як перевірити, що профіль завантажено
  • Побачте, що відбувається, коли умови профілю порушуються
  • Побачте, що відбувається, якщо профіль не може бути завантажено

Перш ніж ви розпочнете

AppArmor — це необовʼязковий модуль ядра та функція Kubernetes, тому переконайтеся, що він підтримується на ваших вузлах перед продовженням:

  1. Модуль ядра AppArmor увімкнено — для того, щоб ядро Linux застосовувало профіль AppArmor, модуль ядра AppArmor повинен бути встановлений та увімкнений. Декілька дистрибутивів стандартно вмикають модуль, такі як Ubuntu і SUSE, і багато інших надають опціональну підтримку. Щоб перевірити, чи модуль увімкнено, перевірте файл /sys/module/apparmor/parameters/enabled:

    cat /sys/module/apparmor/parameters/enabled
    Y
    

    Kubelet перевіряє, чи AppArmor увімкнено на хості, перед тим як допустити Pod з явно налаштованим AppArmor.

  2. Середовище виконання контейнерів підтримує AppArmor — всі загальні середовища виконання контейнерів, що підтримуються Kubernetes, мають підтримку AppArmor, включаючи containerd та CRI-O. Будь ласка, зверніться до відповідної документації середовища та перевірте, що кластер відповідає вимогам до використання AppArmor.

  3. Профіль завантажено — AppArmor застосовується до Podʼа, вказуючи профіль AppArmor, з яким кожен контейнер повинен працювати. Якщо будь-які вказані профілі не завантажені в ядро, Kubelet відхилить Pod. Ви можете переглянути завантажені профілі на вузлі, перевіривши файл /sys/kernel/security/apparmor/profiles. Наприклад:

    ssh gke-test-default-pool-239f5d02-gyn2 "sudo cat /sys/kernel/security/apparmor/profiles | sort"
    
    apparmor-test-deny-write (enforce)
    apparmor-test-audit-write (enforce)
    docker-default (enforce)
    k8s-nginx (enforce)
    

    Для отримання додаткової інформації щодо завантаження профілів на вузли, див. Налаштування вузлів з профілями.

Захист Podʼа

Профілі AppArmor можна вказати на рівні Podʼа або на рівні контейнера. Профіль AppArmor контейнера перевагу перед профілем Podʼа.

securityContext:
  appArmorProfile:
    type: <тип_профілю>

Де <тип_профілю> є одним з:

  • RuntimeDefault для використання типового профілю виконавчого середовища
  • Localhost для використання профілю, завантаженого на хост (див. нижче)
  • Unconfined для запуску без AppArmor

Для отримання повної інформації про API профілю AppArmor див. Вказівки щодо обмеження AppArmor.

Щоб перевірити, що профіль був застосований, ви можете перевірити, що кореневий процес контейнера працює з правильним профілем, переглянувши його атрибут proc:

kubectl exec <імʼя_пода> -- cat /proc/1/attr/current

Вивід повинен виглядати приблизно так:

cri-containerd.apparmor.d (enforce)

Приклад

Цей приклад передбачає, що ви вже налаштували кластер з підтримкою AppArmor.

Спочатку завантажте профіль, який ви хочете використовувати на вузлах. Цей профіль блокує всі операції запису файлів:

#include <tunables/global>

profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
  #include <abstractions/base>

  file,

   # Deny all file writes.
  deny /** w,
}

Профіль повинен бути завантажений на всі вузли, оскільки ви не знаєте, де буде заплановано Pod. Для цього прикладу ви можете використовувати SSH для встановлення профілів, але існують інші методи, які обговорюються в Налаштуваннях вузлів з профілями.

# Цей приклад передбачає, що імена вузлів відповідають іменам хостів і доступні через SSH.
NODES=($( kubectl get node -o jsonpath='{.items[*].status.addresses[?(.type == "Hostname")].address}' ))

for NODE in ${NODES[*]}; do ssh $NODE 'sudo apparmor_parser -q <<EOF
#include <tunables/global>

profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
  #include <abstractions/base>

  file,

  # Заборонити всі записи файлів.
  deny /** w,
}
EOF'
done

Потім запустіть простий Pod "Hello AppArmor" з профілем deny-write:

apiVersion: v1
kind: Pod
metadata:
  name: hello-apparmor
spec:
  securityContext:
    appArmorProfile:
      type: Localhost
      localhostProfile: k8s-apparmor-example-deny-write
  containers:
  - name: hello
    image: busybox:1.28
    command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
kubectl create -f hello-apparmor.yaml

Ви можете перевірити, що контейнер дійсно працює з тим профілем, перевіривши /proc/1/attr/current:

kubectl exec hello-apparmor -- cat /proc/1/attr/current

Вивід повинен бути:

k8s-apparmor-example-deny-write (enforce)

Нарешті, ви можете побачити, що відбудеться, якщо ви порушите профіль, здійснивши спробу запису у файл:

kubectl exec hello-apparmor -- touch /tmp/test
touch: /tmp/test: Permission denied
error: error executing remote command: command terminated with non-zero exit code: Error executing in Docker Container: 1

Щоб завершити, подивіться, що станеться, якщо ви спробуєте вказати профіль, який не був завантажений:

kubectl create -f /dev/stdin <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: hello-apparmor-2
spec:
  securityContext:
    appArmorProfile:
      type: Localhost
      localhostProfile: k8s-apparmor-example-allow-write
  containers:
  - name: hello
    image: busybox:1.28
    command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ]
EOF
pod/hello-apparmor-2 created

Хоча Pod було створено успішно, подальший огляд покаже, що він застряг в стані очікування:

kubectl describe pod hello-apparmor-2
Name:          hello-apparmor-2
Namespace:     default
Node:          gke-test-default-pool-239f5d02-x1kf/10.128.0.27
Start Time:    Tue, 30 Aug 2016 17:58:56 -0700
Labels:        <none>
Annotations:   container.apparmor.security.beta.kubernetes.io/hello=localhost/k8s-apparmor-example-allow-write
Status:        Pending
...
Events:
  Type     Reason     Age              From               Message
  ----     ------     ----             ----               -------
  Normal   Scheduled  10s              default-scheduler  Successfully assigned default/hello-apparmor to gke-test-default-pool-239f5d02-x1kf
  Normal   Pulled     8s               kubelet            Successfully pulled image "busybox:1.28" in 370.157088ms (370.172701ms including waiting)
  Normal   Pulling    7s (x2 over 9s)  kubelet            Pulling image "busybox:1.28"
  Warning  Failed     7s (x2 over 8s)  kubelet            Error: failed to get container spec opts: failed to generate apparmor spec opts: apparmor profile not found k8s-apparmor-example-allow-write
  Normal   Pulled     7s               kubelet            Successfully pulled image "busybox:1.28" in 90.980331ms (91.005869ms including waiting)

Event надає повідомлення про помилку з причиною, конкретне формулювання залежить від виконавчого середовища:

  Warning  Failed     7s (x2 over 8s)  kubelet            Error: failed to get container spec opts: failed to generate apparmor spec opts: apparmor profile not found

Адміністрування

Налаштування вузлів з профілями

Kubernetes 1.32 не надає вбудованих механізмів для завантаження профілів AppArmor на Вузли. Профілі можна завантажити через власну інфраструктуру або інструменти, такі як Оператор профілів безпеки Kubernetes.

Планувальник не знає, які профілі завантажені на який Вузол, тому повний набір профілів повинні бути завантажені на кожен Вузол. Альтернативним підходом є додавання мітки Вузла для кожного профілю (або клас профілів) на Вузлі та використання селектора вузла, щоб забезпечити виконання Podʼа на Вузлі з потрібним профілем.

Створення профілів

Встановлення правильних профілів AppArmor може бути складною справою. На щастя, існують деякі інструменти, що допомагають у цьому:

  • aa-genprof та aa-logprof генерують правила профілю, моніторячи діяльність програми та логи та дозволяючи дії, які вона виконує. Додаткові інструкції надаються у документації AppArmor.
  • bane — генератор профілів AppArmor для Docker, який використовує спрощену мову профілю.

Для дослідження проблем з AppArmor ви можете перевірити системні логи, щоб побачити, що саме було заборонено. AppArmor логує вичерпні повідомлення в dmesg, а помилки зазвичай можна знайти в системних логах або за допомогою journalctl. Більше інформації надається в розділі AppArmor failures.

Вказівки щодо обмеження AppArmor

Профіль AppArmor у контексті безпеки

Ви можете вказати appArmorProfile як у контексті безпеки контейнера, так і в контексті безпеки Podʼа. Якщо профіль встановлено на ні Podʼа, він буде використовуватися як стандартний профіль всіх контейнерів у Podʼі (включаючи контейнери ініціалізації, допоміжний та тимчасовий контейнери). Якщо вказані профілі Podʼа та контейнера, буде використано профіль контейнера.

Профіль AppArmor має 2 поля:

type (обовʼязково) — вказує, який тип профілю AppArmor буде застосований. Дійсні варіанти:

Localhost
профіль, завантажений на вузол (вказаний як localhostProfile).
RuntimeDefault
типовий профіль виконавчого середовища контейнера.
Unconfined
без застосування AppArmor.

localhostProfile — Назва профілю, завантаженого на вузол, який слід використовувати. Профіль повинен бути попередньо налаштований на вузлі, щоб працювати. Цей параметр потрібно вказувати лише в разі, якщо type — Localhost.

Що далі

Додаткові ресурси:

4.4 - Обмеження системних викликів контейнера за допомогою seccomp

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.19 [stable]

Seccomp походить від secure computing mode (безпечний режим обчислення) і є функцією ядра Linux з версії 2.6.12. Його можна використовувати для ізоляції привілеїв процесу, обмежуючи виклики, які він може здійснювати з простору користувача в ядро. Kubernetes дозволяє автоматично застосовувати профілі seccomp, завантажені на вузол, до ваших Podʼів та контейнерів.

Визначення необхідних привілеїв для ваших робочих навантажень може бути важким завданням. У цьому посібнику ви дізнаєтеся, як завантажувати профілі seccomp в локальний кластер Kubernetes, як застосовувати їх до Podʼа та як ви можете почати створювати профілі, які надають лише необхідні привілеї процесам вашого контейнера.

Цілі

  • Навчитися завантажувати профілі seccomp на вузол
  • Навчитися застосовувати профіль seccomp до контейнера
  • Спостерігати за аудитом системних викликів, здійснюваних процесом контейнера
  • Спостерігати поведінку при відсутності вказаного профілю
  • Спостерігати порушення профілю seccomp
  • Навчитися створювати деталізовані профілі seccomp
  • Навчитися застосовувати стандартний профіль seccomp для контейнера

Перш ніж ви розпочнете

Для завершення всіх кроків у цьому посібнику вам потрібно встановити kind та kubectl.

Команди, які використовуються в посібнику, передбачають, що ви використовуєте Docker як ваше середовище для виконання контейнерів. (Кластер, який створює kind, може використовувати інше контейнерне середовище також). Ви також можете використовувати Podman, але у цьому випадку вам доведеться дотримуватися відповідних інструкцій, щоб успішно виконати завдання.

У цьому посібнику показано деякі приклади, які є бета-версією (починаючи з v1.25) і інші, які використовують лише загальнодоступну функціональність seccomp. Вам слід переконатися, що ваш кластер правильно налаштований для версії, яку ви використовуєте.

У посібнику також використовується інструмент curl для завантаження прикладів на ваш компʼютер. Ви можете адаптувати кроки, щоб використовувати інший інструмент, якщо вам це зручно.

Завантаження прикладів профілів seccomp

Вміст цих профілів буде досліджено пізніше, але наразі продовжте та завантажте їх у каталог з назвою profiles/, щоб їх можна було завантажити в кластер.

{
    "defaultAction": "SCMP_ACT_LOG"
}

{
    "defaultAction": "SCMP_ACT_ERRNO"
}

{
    "defaultAction": "SCMP_ACT_ERRNO",
    "architectures": [
        "SCMP_ARCH_X86_64",
        "SCMP_ARCH_X86",
        "SCMP_ARCH_X32"
    ],
    "syscalls": [
        {
            "names": [
                "accept4",
                "epoll_wait",
                "pselect6",
                "futex",
                "madvise",
                "epoll_ctl",
                "getsockname",
                "setsockopt",
                "vfork",
                "mmap",
                "read",
                "write",
                "close",
                "arch_prctl",
                "sched_getaffinity",
                "munmap",
                "brk",
                "rt_sigaction",
                "rt_sigprocmask",
                "sigaltstack",
                "gettid",
                "clone",
                "bind",
                "socket",
                "openat",
                "readlinkat",
                "exit_group",
                "epoll_create1",
                "listen",
                "rt_sigreturn",
                "sched_yield",
                "clock_gettime",
                "connect",
                "dup2",
                "epoll_pwait",
                "execve",
                "exit",
                "fcntl",
                "getpid",
                "getuid",
                "ioctl",
                "mprotect",
                "nanosleep",
                "open",
                "poll",
                "recvfrom",
                "sendto",
                "set_tid_address",
                "setitimer",
                "writev",
                "fstatfs",
                "getdents64",
                "pipe2",
                "getrlimit"
            ],
            "action": "SCMP_ACT_ALLOW"
        }
    ]
}

Виконайте ці команди:

mkdir ./profiles
curl -L -o profiles/audit.json https://k8s.io/examples/pods/security/seccomp/profiles/audit.json
curl -L -o profiles/violation.json https://k8s.io/examples/pods/security/seccomp/profiles/violation.json
curl -L -o profiles/fine-grained.json https://k8s.io/examples/pods/security/seccomp/profiles/fine-grained.json
ls profiles

Ви повинні побачити три профілі, перераховані в кінці останнього кроку:

audit.json  fine-grained.json  violation.json

Створення локального кластера Kubernetes за допомогою kind

Для спрощення можна використовувати kind, щоб створити одновузловий кластер з завантаженими профілями seccomp. Kind запускає Kubernetes в Docker, тому кожен вузол кластера є контейнером. Це дозволяє монтувати файли в файлову систему кожного контейнера, схоже на завантаження файлів на вузол.

apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
nodes:
- role: control-plane
  extraMounts:
  - hostPath: "./profiles"
    containerPath: "/var/lib/kubelet/seccomp/profiles"

Завантажте цей приклад конфігурації kind та збережіть його у файлі з назвою kind.yaml:

curl -L -O https://k8s.io/examples/pods/security/seccomp/kind.yaml

Ви можете встановити конкретну версію Kubernetes, встановивши образ контейнера вузла. Дивіться Вузли в документації kind щодо конфігурації для отримання більш детальної інформації з цього питання. Цей посібник передбачає, що ви використовуєте Kubernetes v1.32.

Як бета-функція, ви можете налаштувати Kubernetes на використання профілю, який обирає стандартне середовище виконання контейнерів, замість того, щоб використовувати Unconfined. Якщо ви хочете спробувати це, дивіться увімкнення використання RuntimeDefault як типового профілю за замовчуванням для всіх завдань перш ніж продовжувати.

Як тільки у вас буде конфігурація kind, створіть кластер kind з цією конфігурацією:

kind create cluster --config=kind.yaml

Після створення нового кластера Kubernetes ідентифікуйте контейнер Docker, що працює як одновузловий кластер:

docker ps

Ви повинні побачити вивід, який вказує на те, що контейнер працює з назвою kind-control-plane, подібний до:

CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                       NAMES
6a96207fed4b        kindest/node:v1.18.2   "/usr/local/bin/entr…"   27 seconds ago      Up 24 seconds       127.0.0.1:42223->6443/tcp   kind-control-plane

Якщо спостерігати файлову систему цього контейнера, ви побачите, що тека profiles/ була успішно завантажена в стандартний шлях seccomp з kubelet. Використовуйте docker exec, щоб виконати команду в Podʼі:

# Змініть 6a96207fed4b на ідентифікатор контейнера, який ви знайдете в результаті "docker ps"
docker exec -it 6a96207fed4b ls /var/lib/kubelet/seccomp/profiles
audit.json  fine-grained.json  violation.json

Ви перевірили, що ці профілі seccomp доступні для kubelet, що працює всередині kind.

Створення Pod, що використовує стандартний профіль seccomp середовища виконання контейнерів

Більшість контейнерних середовищ надають типовий перелік системних викликів, які дозволені або заборонені. Ви можете використовувати ці стандартні налаштування для вашого робочого навантаження, встановивши тип seccomp у контексті безпеки Pod або контейнера на RuntimeDefault.

Ось маніфест Podʼа, який запитує профіль seccomp RuntimeDefault для всіх своїх контейнерів:

apiVersion: v1
kind: Pod
metadata:
  name: default-pod
  labels:
    app: default-pod
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: test-container
    image: hashicorp/http-echo:1.0
    args:
    - "-text=just made some more syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

Створіть цей Pod:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/default-pod.yaml
kubectl get pod default-pod

Под повинен бути запущений успішно:

NAME        READY   STATUS    RESTARTS   AGE
default-pod 1/1     Running   0          20s

Видаліть Pod перед переходом до наступного розділу:

kubectl delete pod default-pod --wait --now

Створення Podʼа з профілем seccomp для аудиту системних викликів

Для початку, застосуйте профіль audit.json, який буде записувати всі системні виклики процесу, до нового Podʼа.

Ось маніфест для цього Podʼа:

apiVersion: v1
kind: Pod
metadata:
  name: audit-pod
  labels:
    app: audit-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/audit.json
  containers:
  - name: test-container
    image: hashicorp/http-echo:1.0
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

Створіть Pod у кластері:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/audit-pod.yaml

Цей профіль не обмежує жодні системні виклики, тому Pod повинен успішно запуститися.

kubectl get pod audit-pod
NAME        READY   STATUS    RESTARTS   AGE
audit-pod   1/1     Running   0          30s

Щоб мати можливість взаємодіяти з цим точкою доступу, створіть Service NodePort, який дозволяє доступ до точки доступу зсередини контейнера панелі управління kind.

kubectl expose pod audit-pod --type NodePort --port 5678

Перевірте, який порт було призначено Service на вузлі.

kubectl get service audit-pod

Вивід буде схожим на:

NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
audit-pod   NodePort   10.111.36.142   <none>        5678:32373/TCP   72s

Тепер ви можете використовувати curl, щоб отримати доступ до цієї точки доступу зсередини контейнера панелі управління kind на порту, який було відкрито цим Service. Використовуйте docker exec, щоб виконати команду curl всередині контейнера панелі управління:

# Змініть 6a96207fed4b на ідентифікатор контейнера панелі управління та 32373 на номер порту, який ви побачили у "docker ps"
docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!

Ви можете бачити, що процес працює, але які саме системні виклики він робить? Оскільки цей Pod працює у локальному кластері, ви повинні бачити їх у /var/log/syslog у вашій локальній системі. Відкрийте нове вікно термінала та використовуйте команду tail, щоб переглянути виклики від http-echo:

# Шлях до логу на вашому компʼютері може відрізнятися від "/var/log/syslog"
tail -f /var/log/syslog | grep 'http-echo'

Ви вже повинні бачити деякі логи системних викликів, зроблених http-echo, і якщо ви знову запустите curl всередині контейнера панелі управління, ви побачите більше записів у лозі.

Наприклад:

Jul  6 15:37:40 my-machine kernel: [369128.669452] audit: type=1326 audit(1594067860.484:14536): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=51 compat=0 ip=0x46fe1f code=0x7ffc0000
Jul  6 15:37:40 my-machine kernel: [369128.669453] audit: type=1326 audit(1594067860.484:14537): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=54 compat=0 ip=0x46fdba code=0x7ffc0000
Jul  6 15:37:40 my-machine kernel: [369128.669455] audit: type=1326 audit(1594067860.484:14538): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000
Jul  6 15:37:40 my-machine kernel: [369128.669456] audit: type=1326 audit(1594067860.484:14539): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=288 compat=0 ip=0x46fdba code=0x7ffc0000
Jul  6 15:37:40 my-machine kernel: [369128.669517] audit: type=1326 audit(1594067860.484:14540): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=0 compat=0 ip=0x46fd44 code=0x7ffc0000
Jul  6 15:37:40 my-machine kernel: [369128.669519] audit: type=1326 audit(1594067860.484:14541): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000
Jul  6 15:38:40 my-machine kernel: [369188.671648] audit: type=1326 audit(1594067920.488:14559): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000
Jul  6 15:38:40 my-machine kernel: [369188.671726] audit: type=1326 audit(1594067920.488:14560): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000

Ви можете почати розуміти системні виклики, необхідні для процесу http-echo, переглядаючи запис syscall= на кожному рядку. Хоча ці виклики навряд чи охоплюють усі системні виклики, які він використовує, це може слугувати основою для профілю seccomp для цього контейнера.

Видаліть Service та Pod перед переходом до наступного розділу:

kubectl delete service audit-pod --wait
kubectl delete pod audit-pod --wait --now

Створення Podʼа з профілем seccomp, що спричиняє порушення правил

Для демонстрації застосуйте до Podʼа профіль, який не дозволяє жодних системних викликів.

Ось маніфест для цієї демонстрації:

apiVersion: v1
kind: Pod
metadata:
  name: violation-pod
  labels:
    app: violation-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/violation.json
  containers:
  - name: test-container
    image: hashicorp/http-echo:1.0
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

Спробуйте створити Pod у кластері:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/violation-pod.yaml

Pod буде створено, але виникне проблема. Якщо ви перевірите стан Podʼа, ви побачите, що його не вдалося запустити.

kubectl get pod violation-pod
NAME            READY   STATUS             RESTARTS   AGE
violation-pod   0/1     CrashLoopBackOff   1          6s

Як видно з попереднього прикладу, процес http-echo потребує досить багато системних викликів. У цьому випадку seccomp було налаштовано на помилку при будь-якому системному виклику, встановивши "defaultAction": "SCMP_ACT_ERRNO". Це надзвичайно безпечно, але робить неможливим виконання будь-яких значущих дій. Насправді ви хочете надати робочим навантаженням тільки ті привілеї, які їм потрібні.

Видаліть Pod перед переходом до наступного розділу:

kubectl delete pod violation-pod --wait --now

Створення Podʼа з профілем seccomp, що дозволяє лише необхідні системні виклики

Якщо ви подивитеся на профіль fine-grained.json, ви помітите деякі з системних викликів, які спостерігалися в syslog у першому прикладі, де профіль встановлював "defaultAction": "SCMP_ACT_LOG". Тепер профіль встановлює "defaultAction": "SCMP_ACT_ERRNO", але явно дозволяє набір системних викликів у блоці "action": "SCMP_ACT_ALLOW". Ідеально, якщо контейнер буде успішно працювати, і ви не побачите жодних повідомлень у syslog.

Маніфест для цього прикладу:

apiVersion: v1
kind: Pod
metadata:
  name: fine-pod
  labels:
    app: fine-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/fine-grained.json
  containers:
  - name: test-container
    image: hashicorp/http-echo:1.0
    args:
    - "-text=just made some syscalls!"
    securityContext:
      allowPrivilegeEscalation: false

Створіть Pod у вашому кластері:

kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/fine-pod.yaml
kubectl get pod fine-pod

Pod має успішно запуститися:

NAME        READY   STATUS    RESTARTS   AGE
fine-pod   1/1     Running   0          30s

Відкрийте нове вікно термінала і використовуйте tail для моніторингу записів лога, які згадують виклики від http-echo:

# Шлях до лога на вашому компʼютері може відрізнятися від "/var/log/syslog"
tail -f /var/log/syslog | grep 'http-echo'

Далі, опублікуйте Pod за допомогою Service NodePort:

kubectl expose pod fine-pod --type NodePort --port 5678

Перевірте, який порт було призначено для цього сервісу на вузлі:

kubectl get service fine-pod

Вихідні дані можуть виглядати приблизно так:

NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
fine-pod    NodePort   10.111.36.142   <none>        5678:32373/TCP   72s

Використовуйте curl для доступу до цієї точки доступу з контейнера панелі управління kind:

# Змініть 6a96207fed4b на ID контейнера панелі управління і 32373 на номер порту, який ви побачили у "docker ps"
docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!

Ви не повинні побачити жодних записів у syslog. Це тому, що профіль дозволив усі необхідні системні виклики та вказав, що повинна статися помилка, якщо буде викликано виклик, який не входить до списку. Це ідеальна ситуація з погляду безпеки, але потребує певних зусиль для аналізу програми. Було б добре, якби існував простий спосіб наблизитися до такої безпеки без стількох зусиль.

Видаліть Service і Pod перед переходом до наступного розділу:

kubectl delete service fine-pod --wait
kubectl delete pod fine-pod --wait --now

Увімкнення використання RuntimeDefault як стандартного профілю seccomp для всіх робочих навантажень

СТАН ФУНКЦІОНАЛУ: Kubernetes v1.27 [stable]

Для використання стандартного профілю seccomp, необхідно запустити kubelet з параметром командного рядка --seccomp-default увімкненим для кожного вузла, де ви хочете його використовувати.

Якщо ця функція увімкнена, kubelet буде використовувати як типовий профіль seccomp RuntimeDefault, який визначений середовищем виконання контейнерів, замість використання режиму Unconfined (seccomp вимкнений). Типові профілі прагнуть забезпечити сильний набір налаштувань безпеки, зберігаючи функціональність робочих навантажень. Можливо, типові профілі відрізняються між середовищами виконання контейнерів та їх версіями випуску, наприклад, порівнюючи профілів CRI-O та containerd.

Деякі робочі навантаження можуть вимагати меншої кількості обмежень системних викликів, ніж інші. Це означає, що вони можуть зазнати невдачі під час виконання навіть із профілем RuntimeDefault. Для помʼякшення таких невдач можна:

  • Запустити робоче навантаження явно як Unconfined.
  • Вимкнути функцію SeccompDefault для вузлів, забезпечуючи, щоб робочі навантаження виконувалися на вузлах, де ця функція вимкнена.
  • Створити власний профіль seccomp для робочого навантаження.

Якщо ви вводите цю функцію до кластера подібного до операційного, проєкт Kubernetes рекомендує увімкнути цю функцію на підмножині ваших вузлів та перевірити виконання робочих навантажень перед повсюдним впровадженням змін.

Детальнішу інформацію про можливу стратегію оновлення та відкату ви можете знайти в повʼязаній пропозиції щодо поліпшення Kubernetes (KEP): Увімкнення seccomp стандартно.

Kubernetes 1.32 дозволяє налаштувати профіль seccomp, який застосовується, коли специфікація для Podʼа не визначає конкретний профіль seccomp. Однак, вам все одно потрібно увімкнути це налаштування як типове для кожного вузла, де ви хочете його використовувати.

Якщо ви працюєте у кластері Kubernetes 1.32 і хочете увімкнути цю функцію, ви можете запустити kubelet з параметром командного рядка --seccomp-default або увімкнути її через файл конфігурації kubelet. Щоб увімкнути цю функцію у kind, переконайтеся, що kind забезпечує мінімально необхідну версію Kubernetes і вмикає функцію SeccompDefault у конфігурації kind:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c
    kubeadmConfigPatches:
      - |
        kind: JoinConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            seccomp-default: "true"        
  - role: worker
    image: kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c
    kubeadmConfigPatches:
      - |
        kind: JoinConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            seccomp-default: "true"        

Якщо кластер готовий, тоді запустіть Pod:

kubectl run --rm -it --restart=Never --image=alpine alpine -- sh

тепер він повинен мати прикріплений типовий профіль seccomp. Це можна перевірити, використовуючи docker exec для запуску crictl inspect для контейнера на вузлі kind:

docker exec -it kind-worker bash -c \
    'crictl inspect $(crictl ps --name=alpine -q) | jq .info.runtimeSpec.linux.seccomp'
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32"],
  "syscalls": [
    {
      "names": ["..."]
    }
  ]
}

Що далі

Ви можете дізнатися більше про Linux seccomp:

5 - Stateless застосунки

5.1 - Відкриття зовнішньої IP-адреси для доступу до застосунку в кластері

Ця сторінка показує, як створити обʼєкт Kubernetes Service, який відкриває зовнішню IP-адресу.

Перш ніж ви розпочнете

  • Встановіть kubectl.
  • Використовуйте хмарного провайдера, такого як Google Kubernetes Engine або Amazon Web Services, щоб створити кластер Kubernetes. У цьому підручнику створюється зовнішній балансувальник навантаження, який вимагає хмарного провайдера.
  • Налаштуйте kubectl для спілкування з вашим API-сервером Kubernetes. Для інструкцій дивіться документацію вашого хмарного провайдера.

Цілі

  • Запустіть пʼять екземплярів застосунку Hello World.
  • Створіть обʼєкт Service, який відкриває зовнішню IP-адресу.
  • Використовуйте обʼєкт Service для доступу до запущеного застосунку.

Створення Service для застосунку, що працює в пʼяти Podʼах

  1. Запустіть застосунок Hello World у вашому кластері:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app.kubernetes.io/name: load-balancer-example
      name: hello-world
    spec:
      replicas: 5
      selector:
        matchLabels:
          app.kubernetes.io/name: load-balancer-example
      template:
        metadata:
          labels:
            app.kubernetes.io/name: load-balancer-example
        spec:
          containers:
          - image: gcr.io/google-samples/hello-app:2.0
            name: hello-world
            ports:
            - containerPort: 8080
    
    kubectl apply -f https://k8s.io/examples/service/load-balancer-example.yaml
    

    Попередня команда створює Deployment і повʼязаний з ним ReplicaSet. ReplicaSet має пʼять Podʼів, кожен з яких запускає застосунок Hello World.

  2. Виведіть інформацію про Deployment:

    kubectl get deployments hello-world
    kubectl describe deployments hello-world
    
  3. Виведіть інформацію про обʼєкти ReplicaSet:

    kubectl get replicasets
    kubectl describe replicasets
    
  4. Створіть обʼєкт Service, який експонує Deployment:

    kubectl expose deployment hello-world --type=LoadBalancer --name=my-service
    
  5. Виведіть інформацію про Service:

    kubectl get services my-service
    

    Вивід буде подібний до:

    NAME         TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)    AGE
    my-service   LoadBalancer   10.3.245.137   104.198.205.71   8080/TCP   54s
    
  6. Виведіть детальну інформацію про Service:

    kubectl describe services my-service
    

    Вивід буде подібний до:

    Name:           my-service
    Namespace:      default
    Labels:         app.kubernetes.io/name=load-balancer-example
    Annotations:    <none>
    Selector:       app.kubernetes.io/name=load-balancer-example
    Type:           LoadBalancer
    IP:             10.3.245.137
    LoadBalancer Ingress:   104.198.205.71
    Port:           <unset> 8080/TCP
    NodePort:       <unset> 32377/TCP
    Endpoints:      10.0.0.6:8080,10.0.1.6:8080,10.0.1.7:8080 + 2 more...
    Session Affinity:   None
    Events:         <none>
    

    Занотуйте зовнішню IP-адресу (LoadBalancer Ingress), відкриту вашим сервісом. У цьому прикладі, зовнішня IP-адреса — 104.198.205.71. Також занотуйте значення Port і NodePort. У цьому прикладі, Port — 8080, а NodePort — 32377.

  7. У попередньому виводі ви можете побачити, що сервіс має кілька точок доступу: 10.0.0.6:8080, 10.0.1.6:8080, 10.0.1.7:8080 + ще 2. Це внутрішні адреси Podʼів, які запускають застосунок Hello World. Щоб переконатися, що це адреси Podʼів, введіть цю команду:

    kubectl get pods --output=wide
    

    Вивід буде подібний до:

    NAME                         ...  IP         NODE
    hello-world-2895499144-1jaz9 ...  10.0.1.6   gke-cluster-1-default-pool-e0b8d269-1afc
    hello-world-2895499144-2e5uh ...  10.0.1.8   gke-cluster-1-default-pool-e0b8d269-1afc
    hello-world-2895499144-9m4h1 ...  10.0.0.6   gke-cluster-1-default-pool-e0b8d269-5v7a
    hello-world-2895499144-o4z13 ...  10.0.1.7   gke-cluster-1-default-pool-e0b8d269-1afc
    hello-world-2895499144-segjf ...  10.0.2.5   gke-cluster-1-default-pool-e0b8d269-cpuc
    
  8. Використовуйте зовнішню IP-адресу (LoadBalancer Ingress) для доступу до застосунку Hello World:

    curl http://<external-ip>:<port>
    

    де <external-ip> — це зовнішня IP-адреса (LoadBalancer Ingress) вашого сервісу, а <port> — значення Port в описі вашого сервісу. Якщо ви використовуєте minikube, ввівши minikube service my-service автоматично відкриє застосунок Hello World в оглядачі.

    Відповідь на успішний запит — привітальне повідомлення:

    Hello, world!
    Version: 2.0.0
    Hostname: 0bd46b45f32f
    

Очищення

Щоб видалити Service, введіть цю команду:

kubectl delete services my-service

Щоб видалити Deployment, ReplicaSet і Podʼи, які запускають застосунок Hello World, введіть цю команду:

kubectl delete deployment hello-world

Що далі

Дізнайтеся більше про Підключення застосунків за допомогою Service.

5.2 - Приклад: Розгортання PHP застосунку гостьової книги з Redis

Цей посібник показує, як створити та розгорнути простий (але не готовий до промислового використання) багатошаровий вебзастосунок, використовуючи Kubernetes та Docker. Цей приклад складається з наступних компонентів:

  • Одно-екземплярний Redis для зберігання записів у гостьовій книзі
  • Кілька веб-фронтендів

Цілі

  • Запустити Redis-лідер.
  • Запустити двох Redis-фолловерів.
  • Запустити фронтенд гостьової книги.
  • Опублікувати та переглянути Frontend Service.
  • Виконати очищення.

Перш ніж ви розпочнете

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Версія вашого Kubernetes сервера має бути не старішою ніж v1.14. Для перевірки версії введіть kubectl version.

Запуск бази даних Redis

Застосунок гостьової книги використовує Redis для зберігання своїх даних.

Створення Deployment Redis

Файл маніфесту, наведений нижче, визначає контролер Deployment, який запускає одну репліку Redis Pod.

# SOURCE: https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-leader
  labels:
    app: redis
    role: leader
    tier: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
        role: leader
        tier: backend
    spec:
      containers:
      - name: leader
        image: "docker.io/redis:6.0.5"
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        ports:
        - containerPort: 6379
  1. Відкрийте вікно термінала в теці, куди ви завантажили файли маніфестів.

  2. Застосуйте Deployment Redis з файлу redis-leader-deployment.yaml:

    kubectl apply -f https://k8s.io/examples/application/guestbook/redis-leader-deployment.yaml
    
  3. Перевірте список Podʼів, щоб переконатися, що Pod Redis запущений:

    kubectl get pods
    

    Відповідь повинна бути схожою на цю:

    NAME                           READY   STATUS    RESTARTS   AGE
    redis-leader-fb76b4755-xjr2n   1/1     Running   0          13s
    
  4. Виконайте наступну команду, щоб переглянути лог з Podʼа Redis лідера:

    kubectl logs -f deployment/redis-leader
    

Створення Service Redis-лідера

Застосунок гостьової книги потребує звʼязку з Redis для запису своїх даних. Вам потрібно застосувати Service, щоб спрямовувати трафік до Pod Redis. Service визначає політику доступу до Podʼів.

# SOURCE: https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion: v1
kind: Service
metadata:
  name: redis-leader
  labels:
    app: redis
    role: leader
    tier: backend
spec:
  ports:
  - port: 6379
    targetPort: 6379
  selector:
    app: redis
    role: leader
    tier: backend
  1. Застосуйте Service Redis з файлу redis-leader-service.yaml:

    kubectl apply -f https://k8s.io/examples/application/guestbook/redis-leader-service.yaml
    
  2. Перевірте список Serviceʼів, щоб переконатися, що Service Redis запущений:

    kubectl get service
    

    Відповідь повинна бути схожою на цю:

    NAME           TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
    kubernetes     ClusterIP   10.0.0.1     <none>        443/TCP    1m
    redis-leader   ClusterIP   10.103.78.24 <none>        6379/TCP   16s
    

Налаштування Redis-фолловерів

Хоча Redis-лідер є одним Pod, ви можете зробити його високо доступним і задовольняти потреби в трафіку, додавши кілька фолловерів Redis або реплік.

# SOURCE: https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-follower
  labels:
    app: redis
    role: follower
    tier: backend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
        role: follower
        tier: backend
    spec:
      containers:
      - name: follower
        image: us-docker.pkg.dev/google-samples/containers/gke/gb-redis-follower:v2
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        ports:
        - containerPort: 6379
  1. Застосуйте Deployment Redis з файлу redis-follower-deployment.yaml:

    kubectl apply -f https://k8s.io/examples/application/guestbook/redis-follower-deployment.yaml
    
  2. Перевірте, що дві репліки Redis-фолловерів запущені, виконавши запит списку Podʼів:

    kubectl get pods
    

    Відповідь повинна бути схожою на цю:

    NAME                             READY   STATUS    RESTARTS   AGE
    redis-follower-dddfbdcc9-82sfr   1/1     Running   0          37s
    redis-follower-dddfbdcc9-qrt5k   1/1     Running   0          38s
    redis-leader-fb76b4755-xjr2n     1/1     Running   0          11m
    

Створення Service Redis-фолловера

Застосунок гостьової книги потребує зʼязку з фолловерами Redis для читання даних. Щоб зробити фолловерів Redis доступними, потрібно налаштувати інший Service.

# SOURCE: https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion: v1
kind: Service
metadata:
  name: redis-follower
  labels:
    app: redis
    role: follower
    tier: backend
spec:
  ports:
    # порт на якому повинен працювати цей Service
  - port: 6379
  selector:
    app: redis
    role: follower
    tier: backend
  1. Застосуйте сервіс Redis з файлу redis-follower-service.yaml:

    kubectl apply -f https://k8s.io/examples/application/guestbook/redis-follower-service.yaml
    
  2. Перевірте список Serviceʼів, щоб переконатися, що Service Redis запущений:

    kubectl get service
    

    Відповідь повинна бути схожою на цю:

    NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
    kubernetes       ClusterIP   10.96.0.1       <none>        443/TCP    3d19h
    redis-follower   ClusterIP   10.110.162.42   <none>        6379/TCP   9s
    redis-leader     ClusterIP   10.103.78.24    <none>        6379/TCP   6m10s
    

Налаштування та експонування фронтенда гостьової книги

Тепер, коли ви запустили сховище Redis для своєї гостьової книги, запустіть вебсервери фронтенду. Як і фолловери Redis, фронтенд розгортається за допомогою контролера Deployment Kubernetes.

Застосунок гостьової книги використовує PHP фронтенд. Він налаштований для звʼязку з Serviceʼами фолловера або лідера Redis, залежно від того, чи є запит читанням або записом. Фронтенд відкриває інтерфейс JSON та забезпечує UX на основі jQuery-Ajax.

Створення Deployment фронтенду гостьової книги

# SOURCE: https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
        app: guestbook
        tier: frontend
  template:
    metadata:
      labels:
        app: guestbook
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: us-docker.pkg.dev/google-samples/containers/gke/gb-frontend:v5
        env:
        - name: GET_HOSTS_FROM
          value: "dns"
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        ports:
        - containerPort: 80
  1. Застосуйте розгортання фронтенду з файлу frontend-deployment.yaml:

    kubectl apply -f https://k8s.io/examples/application/guestbook/frontend-deployment.yaml
    
  2. Перевірте список Podʼів, щоб переконатися, що три репліки фронтенду запущені:

    kubectl get pods -l app=guestbook -l tier=frontend
    

    Відповідь повинна бути схожою на цю:

    NAME                        READY   STATUS    RESTARTS   AGE
    frontend-85595f5bf9-5tqhb   1/1     Running   0          47s
    frontend-85595f5bf9-qbzwm   1/1     Running   0          47s
    frontend-85595f5bf9-zchwc   1/1     Running   0          47s
    

Створення Service для Фронтенду

Serviceʼи Redis, які ви створили, доступні тільки всередині кластера Kubernetes, оскільки типовий тип для Service — ClusterIP. ClusterIP надає одну IP-адресу для набору Podʼів, на які вказує Service. Ця IP-адреса доступна тільки всередині кластера.

Якщо ви хочете, щоб гості могли отримати доступ до вашої гостьової книги, ви повинні налаштувати Service фронтенду таким чином, щоб він був видимий зовні, щоб клієнт міг запитувати Service ззовні кластера Kubernetes. Проте користувачі Kubernetes можуть скористатися командою kubectl port-forward, щоб отримати доступ до Service, навіть якщо він використовує ClusterIP.

# SOURCE: https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion: v1
kind: Service
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # якщо ваш кластер підтримує це, розкоментуйте наступне, щоб автоматично створити
  # зовнішню IP-адресу з балансуванням навантаження для служби frontend.
  # type: LoadBalancer
  #type: LoadBalancer
  ports:
    # порт на якому має працювати цей Service
  - port: 80
  selector:
    app: guestbook
    tier: frontend
  1. Застосуйте Service фронтенду з файлу frontend-service.yaml:

    kubectl apply -f https://k8s.io/examples/application/guestbook/frontend-service.yaml
    
  2. Виконайте запит для отримання списку Service, щоб переконатися, що Service фронтенду запущений:

    kubectl get services
    

    Відповідь повинна бути схожою на цю:

    NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
    frontend         ClusterIP   10.97.28.230    <none>        80/TCP     19s
    kubernetes       ClusterIP   10.96.0.1       <none>        443/TCP    3d19h
    redis-follower   ClusterIP   10.110.162.42   <none>        6379/TCP   5m48s
    redis-leader     ClusterIP   10.103.78.24    <none>        6379/TCP   11m
    

Перегляд Service Фронтенду через kubectl port-forward

  1. Виконайте наступну команду, щоб перенаправити порт 8080 на вашій локальній машині на порт 80 на сервісі.

    kubectl port-forward svc/frontend 8080:80
    

    Відповідь повинна бути схожою на цю:

    Forwarding from 127.0.0.1:8080 -> 80
    Forwarding from [::1]:8080 -> 80
    
  2. Завантажте сторінку http://localhost:8080 у вашому оглядачі, щоб переглянути вашу гостьову книгу.

Перегляд Service Фронтенду через LoadBalancer

Якщо ви розгорнули маніфест frontend-service.yaml з типом LoadBalancer, вам потрібно знайти IP-адресу, щоб переглянути вашу гостьову книгу.

  1. Виконайте наступну команду, щоб отримати IP-адресу для Service фронтенду.

    kubectl get service frontend
    

    Відповідь повинна бути схожою на цю:

    NAME       TYPE           CLUSTER-IP      EXTERNAL-IP        PORT(S)        AGE
    frontend   LoadBalancer   10.51.242.136   109.197.92.229     80:32372/TCP   1m
    
  2. Скопіюйте зовнішню IP-адресу та завантажте сторінку у вашому оглядачі, щоб переглянути вашу гостьову книгу.

Масштабування Веб-Фронтенду

Ви можете збільшувати або зменшувати кількість реплік за потребою, оскільки ваші сервери визначені як Service, що використовує контролер Deployment.

  1. Виконайте наступну команду, щоб збільшити кількість Podʼів фронтенду:

    kubectl scale deployment frontend --replicas=5
    
  2. Виконайте запит для отримання списку Podʼів, щоб перевірити кількість запущених Podʼів фронтенду:

    kubectl get pods
    

    Відповідь повинна виглядати приблизно так:

    NAME                             READY   STATUS    RESTARTS   AGE
    frontend-85595f5bf9-5df5m        1/1     Running   0          83s
    frontend-85595f5bf9-7zmg5        1/1     Running   0          83s
    frontend-85595f5bf9-cpskg        1/1     Running   0          15m
    frontend-85595f5bf9-l2l54        1/1     Running   0          14m
    frontend-85595f5bf9-l9c8z        1/1     Running   0          14m
    redis-follower-dddfbdcc9-82sfr   1/1     Running   0          97m
    redis-follower-dddfbdcc9-qrt5k   1/1     Running   0          97m
    redis-leader-fb76b4755-xjr2n     1/1     Running   0          108m
    
  3. Виконайте наступну команду, щоб зменшити кількість Pods фронтенду:

    kubectl scale deployment frontend --replicas=2
    
  4. Виконайте запит для отримання списку Podʼів, щоб перевірити кількість запущених Podʼів фронтенду:

    kubectl get pods
    

    Відповідь повинна виглядати приблизно так:

    NAME                             READY   STATUS    RESTARTS   AGE
    frontend-85595f5bf9-cpskg        1/1     Running   0          16m
    frontend-85595f5bf9-l9c8z        1/1     Running   0          15m
    redis-follower-dddfbdcc9-82sfr   1/1     Running   0          98m
    redis-follower-dddfbdcc9-qrt5k   1/1     Running   0          98m
    redis-leader-fb76b4755-xjr2n     1/1     Running   0          109m
    

Очищення

Видалення Deployment та Serviceʼів також видаляє всі запущені Podʼи. Використовуйте мітки для видалення кількох ресурсів однією командою.

  1. Виконайте наступні команди, щоб видалити всі Pods, Deployment і Serviceʼи.

    kubectl delete deployment -l app=redis
    kubectl delete service -l app=redis
    kubectl delete deployment frontend
    kubectl delete service frontend
    

    Відповідь повинна виглядати приблизно так:

    deployment.apps "redis-follower" deleted
    deployment.apps "redis-leader" deleted
    deployment.apps "frontend" deleted
    service "frontend" deleted
    
  2. Виконайте запит списку Podʼів, щоб переконатися, що жоден Pod не працює:

    kubectl get pods
    

    Відповідь повинна виглядати приблизно так:

    No resources found in default namespace.
    

Що далі

6 - Stateful застосунки

6.1 - Основи StatefulSet

Цей підручник надає вступ до управління застосунками за допомогою StatefulSets. Він демонструє, як створювати, видаляти, масштабувати та оновлювати Podʼи StatefulSets.

Перш ніж ви розпочнете

Перш ніж розпочати цей підручник, вам слід ознайомитися з наступними концепціями Kubernetes:

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Вам слід налаштувати kubectl для використання контексту, який використовує простір імен default. Якщо ви використовуєте наявний кластер, переконайтеся, що можна використовувати простір імен цього кластера для практики. Ідеальною буде практика в кластері, де не запущені реальні робочі навантаження.

Також корисно прочитати сторінку з концепціями про StatefulSets.

Цілі

StatefulSets призначені для використання з застосунками, які зберігаються свій стан, та розподіленими системами. Однак, адміністрування стану застосунків та розподілених систем у Kubernetes є широкою та складною темою. Щоб продемонструвати базові можливості StatefulSet і не змішувати першу тему з другою, ви розгорнете простий вебзастосунок за допомогою StatefulSet.

Після цього уроку ви будете знайомі з наступним.

  • Як створити StatefulSet
  • Як StatefulSet керує своїми Podʼами
  • Як видалити StatefulSet
  • Як масштабувати StatefulSet
  • Як оновлювати Podʼи StatefulSet

Створення StatefulSet

Почніть зі створення StatefulSet (і Service, на який він спирається) за допомогою наведеного нижче прикладу. Він схожий на приклад, наведений у концепції StatefulSets. Він створює headless Service, nginx, щоб опублікувати IP-адреси Podʼів у StatefulSet, web.

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: registry.k8s.io/nginx-slim:0.21
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

Вам знадобляться принаймні два термінали. У першому терміналі використовуйте kubectl get для спостереження (watch) за створенням Podʼів StatefulSet.

# використовуйте цей термінал для виконання команд із зазначенням --watch
# завершіть цей watch, коли вам буде запропоновано розпочати новий watch
kubectl get pods --watch -l app=nginx

У другому терміналі використовуйте kubectl apply для створення headless Service та StatefulSet:

kubectl apply -f https://k8s.io/examples/application/web/web.yaml
service/nginx created
statefulset.apps/web created

Наведена вище команда створює два Podʼи, кожен з яких запускає вебсервер NGINX. Отримайте інформацію про nginx Service…

kubectl get service nginx
NAME      TYPE         CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx     ClusterIP    None         <none>        80/TCP    12s

…потім отримайте інформацію про web StatefulSet, щоб переконатися, що обидва були створені успішно:

kubectl get statefulset web
NAME   READY   AGE
web    2/2     37s

Упорядковане створення Podʼів

Типово StatefulSet створює свої Podʼи в строгому порядку.

Для StatefulSet з n репліками, коли Podʼи розгортаються, вони створюються послідовно, впорядковані від {0..n-1}. Перегляньте вивід команди kubectl get у першому терміналі. Зрештою вивід буде виглядати як у наведеному нижче прикладі.

# Не починайте новий watch;
# це вже повинно працювати
kubectl get pods --watch -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     0/1       Pending   0          0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         19s
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         18s

Зверніть увагу, що Pod web-1 не запускається, поки Pod web-0 не буде Running (див. Фази Pod) та Ready (див. type у Стани Pod).

Пізніше в цьому підручнику ви будете практикувати паралельний запуск.

Podʼи в StatefulSet

Podʼи в StatefulSet мають унікальний порядковий індекс та стабільну мережеву ідентичність.

Перевірка порядкового індексу Podʼів

Отримайте Podʼи StatefulSet:

kubectl get pods -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          1m
web-1     1/1       Running   0          1m

Як зазначено в концепції StatefulSets, Podʼи в StatefulSet мають фіксовану, унікальну ідентичність. Ця ідентичність базується на унікальному порядковому індексі, який призначається кожному Podʼу контролером StatefulSet controller. Імена Podʼів приймають форму <імʼя statefulset>-<порядковий індекс>. Оскільки StatefulSet web має дві репліки, він створює два Podʼи, web-0 та web-1.

Використання стабільних мережевих ідентичностей

Кожен Pod має стабільне імʼя хосту на основі свого порядкового індексу. Використовуйте kubectl exec для виконання команди hostname в кожному Podʼі:

for i in 0 1; do kubectl exec "web-$i" -- sh -c 'hostname'; done
web-0
web-1

Використайте kubectl run для запуску контейнера, який надає команду nslookup з пакунка dnsutils. Використовуючи nslookup з іменами хостів Podʼів, ви можете переглянути їх внутрішні адреси DNS:

kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm

що запускає нову оболонку. У цій новій оболонці запустіть:

# Виконайте це в оболонці контейнера dns-test
nslookup web-0.nginx

Вивід схожий на:

Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.244.1.6

nslookup web-1.nginx
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.244.2.6

(і тепер вийдіть з оболонки контейнера: exit)

CNAME headless сервісу вказує на SRV записи (один для кожного Podʼа, який виконується та готовий). SRV записи вказують на записи A, які містять IP-адреси Podʼів.

У одному терміналі спостерігайте за Podʼами StatefulSet:

# Розпочніть новий watch
# Завершіть цей watch, коли бачите, що видалення завершено
kubectl get pod --watch -l app=nginx

У другому терміналі використовуйте kubectl delete для видалення всіх Podʼів у StatefulSet:

kubectl delete pod -l app=nginx
Pod "web-0" видалено
Pod "web-1" видалено

Чекайте, поки StatefulSet перезапустить їх, і обидва Podʼи перейдуть до стану Running та Ready:

# Це вже має працювати
kubectl get pod --watch -l app=nginx
NAME      READY     STATUS              RESTARTS   AGE
web-0     0/1       ContainerCreating   0          0s
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          2s
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         34s

Використовуйте kubectl exec та kubectl run, щоб переглянути імена хостів Podʼів та внутрішні DNS-записи. Спочатку перегляньте імена хостів Podʼів:

for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done
web-0
web-1

потім, запустіть:

kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm

що запускає новиe оболонку. У цій новій оболонці запустіть:

# Виконайте це в оболонці контейнера dns-test
nslookup web-0.nginx

Вивід схожий на:

Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.244.1.7

nslookup web-1.nginx
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.244.2.8

(і тепер вийдіть з оболонки контейнера: exit)

Порядкові номери, імена хостів Podʼів, SRV записи та імена записів A не змінилися, але IP-адреси, повʼязані з Podʼами, можуть змінюватися. У кластері, який використовується для цього навчального посібника, вони змінюються. Тому важливо не налаштовувати інші застосунки на підʼєднання до Podʼів у StatefulSet за IP-адресою конкретного Podʼа (можна підключатися до Podʼів, за їх іменем хосту).

Виявлення конкретних Podʼів у StatefulSet

Якщо вам потрібно знайти та підʼєднатись до активних учасників StatefulSet, вам слід запитувати CNAME headless Service (nginx.default.svc.cluster.local). SRV записи, повʼязані з CNAME, будуть містити лише Podʼ у StatefulSet, які виконуються та готові.

Якщо ваш застосунок вже реалізував логіку підʼєднання, яка перевіряє працездатність та готовність, ви можете використовувати SRV записи Podʼів (web-0.nginx.default.svc.cluster.local, web-1.nginx.default.svc.cluster.local), оскільки вони стабільні, і ваш застосунок зможе виявляти адреси Podʼів, коли вони переходять до стану Running та Ready.

Якщо ваш застосунок хоче знайти будь-який здоровий Pod у StatefulSet і, отже, не потрібно відстежувати кожний конкретний Pod, ви також можете підʼєднатись до IP-адреси type: ClusterIP сервісу, Podʼів в цьому StatefulSet. Ви можете використовувати той самий Service, який відстежує StatefulSet (вказаний у serviceName StatefulSet) або окремий Service, який вибирає відповідний набір Podʼів.

Запис у стійке сховище

Отримайте PersistentVolumeClaims для web-0 та web-1:

kubectl get pvc -l app=nginx

Вивід схожий на:

NAME        STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
www-web-0   Bound     pvc-15c268c7-b507-11e6-932f-42010a800002   1Gi        RWO           48s
www-web-1   Bound     pvc-15c79307-b507-11e6-932f-42010a800002   1Gi        RWO           48s

Контролер StatefulSet створив два PersistentVolumeClaims, які привʼязані до двох PersistentVolumes.

Оскільки кластер, використаний у цьому посібнику, налаштований на динамічну підготовку PersistentVolumes, PersistentVolumes були створені та привʼязані автоматично.

NGINX вебсервер типово обслуговує індексний файл із /usr/share/nginx/html/index.html. Поле volumeMounts в spec StatefulSet забезпечує, що тека /usr/share/nginx/html є зберігається у PersistentVolume.

Запишіть імена хостів Podʼів у їх файли index.html та перевірте, що вебсервери NGINX обслуговують імена хостів:

for i in 0 1; do kubectl exec "web-$i" -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'; done

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

У одному терміналі спостерігайте за Podʼами StatefulSet:

# Завершіть цей watch, коли ви дійдете до кінця розділу.
# На початку "Scaling a StatefulSet" ви розпочнете новий watch.
kubectl get pod --watch -l app=nginx

У другому терміналі видаліть всі Podʼи StatefulSet:

kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted

Дослідіть вивід команди kubectl get у першому терміналі та зачекайте, поки всі Podʼи перейдуть до стану Running та Ready.

# Це вже має працювати
kubectl get pod --watch -l app=nginx
NAME      READY     STATUS              RESTARTS   AGE
web-0     0/1       ContainerCreating   0          0s
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          2s
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         34s

Перевірте, чи продовжують вебсервери обслуговувати свої імена хостів:

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

Навіть якщо web-0 та web-1 переплановані, вони продовжують обслуговувати свої імена хостів, оскільки PersistentVolumes, повʼязані з їхніми PersistentVolumeClaims, знову монтується до їхніх volumeMounts. Незалежно від того, на якому вузлі заплановані web-0 та web-1, їхні PersistentVolumes будуть підключені до відповідних точок монтування.

Масштабування StatefulSet

Масштабування StatefulSet передбачає збільшення або зменшення кількості реплік (горизонтальне масштабування). Це досягається шляхом оновлення поля replicas. Ви можете використовувати або kubectl scale, або kubectl patch, щоб масштабувати StatefulSet.

Збільшення масштабу

Збільшення масштабу означає додавання додаткових реплік. Забезпечивши те, що ваш застосунок може розподіляти роботу між StatefulSet, новий більший набір Podʼів може виконувати більше цієї роботи.

У одному вікні термінала спостерігайте за Podʼами у StatefulSet:

# Якщо у вас вже запущений watch, ви можете продовжити його використовувати.
# В іншому випадку запустіть один.
# Закінчіть цей watch, коли для StatefulSet буде 5 справних Podʼів
kubectl get pods --watch -l app=nginx

У іншому вікні термінала використовуйте kubectl scale, щоб змінити кількість реплік на 5:

kubectl scale sts web --replicas=5
statefulset.apps/web scaled

Дослідіть вивід команди kubectl get у першому терміналі та зачекайте, доки три додаткові Podʼи перейдуть у стан Running та Ready.

# Це вже має працювати
kubectl get pod --watch -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          2h
web-1     1/1       Running   0          2h
NAME      READY     STATUS    RESTARTS   AGE
web-2     0/1       Pending   0          0s
web-2     0/1       Pending   0         0s
web-2     0/1       ContainerCreating   0         0s
web-2     1/1       Running   0         19s
web-3     0/1       Pending   0         0s
web-3     0/1       Pending   0         0s
web-3     0/1       ContainerCreating   0         0s
web-3     1/1       Running   0         18s
web-4     0/1       Pending   0         0s
web-4     0/1       Pending   0         0s
web-4     0/1       ContainerCreating   0         0s
web-4     1/1       Running   0         19s

Контролер StatefulSet змінив кількість реплік. Як і при створенні StatefulSet, контролер StatefulSet створював кожен Pod послідовно з урахуванням його порядкового індексу, і чекав, доки попередній Pod перейде у стан Running та Ready перед запуском наступного Podʼа.

Зменшення масштабу

Зменшення масштабу означає зменшення кількості реплік. Наприклад, ви можете це зробити через те, що рівень трафіку до служби зменшився, і на поточному масштабі є ресурси, що простоюють.

У одному вікні термінала спостерігайте за Podʼами у StatefulSet:

# Закінчіть цей watch, коли лишиться лише 3 Podʼи для StatefulSet
kubectl get pod --watch -l app=nginx

У іншому вікні термінала використовуйте kubectl patch, щоб зменшити кількість реплік StatefulSet до трьох:

kubectl patch sts web -p '{"spec":{"replicas":3}}'
statefulset.apps/web patched

Зачекайте, доки web-4 і web-3 перейдуть у стан Terminating.

# Це вже має працювати
kubectl get pods --watch -l app=nginx
NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          3h
web-1     1/1       Running             0          3h
web-2     1/1       Running             0          55s
web-3     1/1       Running             0          36s
web-4     0/1       ContainerCreating   0          18s
NAME      READY     STATUS    RESTARTS   AGE
web-4     1/1       Running   0          19s
web-4     1/1       Terminating   0         24s
web-4     1/1       Terminating   0         24s
web-3     1/1       Terminating   0         42s
web-3     1/1       Terminating   0         42s

Послідовне завершення Podʼів

Панель управління видаляє кожний Pod по одному, у зворотньому порядку щодо його порядкового індексу, і чекає, доки кожен Pod повністю не зупиниться перед видаленням наступного.

Отримайте PersistentVolumeClaims StatefulSet:

kubectl get pvc -l app=nginx
NAME        STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
www-web-0   Bound     pvc-15c268c7-b507-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-1   Bound     pvc-15c79307-b507-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-2   Bound     pvc-e1125b27-b508-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-3   Bound     pvc-e1176df6-b508-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-4   Bound     pvc-e11bb5f8-b508-11e6-932f-42010a800002   1Gi        RWO           13h

Ще існують пʼять PersistentVolumeClaims та пʼять PersistentVolumes. Під час дослідження стабільного сховища Podʼа, ви бачили, що PersistentVolumes, змонтовані у Podʼи StatefulSet, не видаляються, коли Podʼи StatefulSet видаляються. Це також вірно, коли видалення Podʼів викликано зменшенням масштабу StatefulSet.

Оновлення StatefulSets

Контролер StatefulSet підтримує автоматизоване оновлення. Стратегія, яка використовується, визначається полем spec.updateStrategy обʼєкта API StatefulSet. Ця функція може бути використана для оновлення образів контейнерів, запитів ресурсів та/або лімітів, міток та анотацій Podʼів у StatefulSet.

Існують дві стратегії оновлення: RollingUpdate (типово) та OnDelete.

RollingUpdate

Стратегія оновлення RollingUpdate оновлює всі Podʼи в StatefulSet у зворотньому порядку щодо їх порядкового індексу, з дотриманням гарантій StatefulSet.

Ви можете розділити оновлення StatefulSet, який використовує стратегію RollingUpdate, на партиції, вказавши .spec.updateStrategy.rollingUpdate.partition. Ви будете практикувати це пізніше у цьому посібнику.

Спочатку спробуйте просте поетапне оновлення.

В одному вікні термінала відредагуйте StatefulSet web, щоб знову змінити образ контейнера:

kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"registry.k8s.io/nginx-slim:0.24"}]'
statefulset.apps/web patched

В іншому терміналі спостерігайте за Podʼами у StatefulSet:

# Закінчіть це спостереження, коли відбудеться розгортання
#
# Якщо ви не впевнені, залиште його ще на одну хвилину
kubectl get pod -l app=nginx --watch

Вивід буде подібний до:

NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          7m
web-1     1/1       Running   0          7m
web-2     1/1       Running   0          8m
web-2     1/1       Terminating   0         8m
web-2     1/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Pending   0         0s
web-2     0/1       Pending   0         0s
web-2     0/1       ContainerCreating   0         0s
web-2     1/1       Running   0         19s
web-1     1/1       Terminating   0         8m
web-1     0/1       Terminating   0         8m
web-1     0/1       Terminating   0         8m
web-1     0/1       Terminating   0         8m
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         6s
web-0     1/1       Terminating   0         7m
web-0     1/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Pending   0         0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         10s

Podʼи в StatefulSet оновлюються у зворотньому порядку щодо їх порядкового індексу. Контролер StatefulSet завершує кожен Pod і чекає, доки він не перейде в стан Running та Ready, перед тим як оновити наступний Pod. Зверніть увагу, що, навіть якщо контролер StatefulSet не продовжить оновлювати наступний Pod, поки його порядковий наступник не буде Running та Ready, він відновить будь-який Pod, який зазнав помилки під час оновлення до попередньої версії.

Podʼи, які вже отримали оновлення, будуть відновлені до оновленої версії, а Podʼи, які ще не отримали оновлення, будуть відновлені до попередньої версії. Таким чином, контролер намагається продовжувати забезпечувати працездатність застосунку та зберігати оновлення в однорідному стані при наявності тимчасових збоїв.

Отримайте Podʼи, щоб переглянути їх образи контейнерів:

for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
registry.k8s.io/nginx-slim:0.24
registry.k8s.io/nginx-slim:0.24
registry.k8s.io/nginx-slim:0.24

Всі Podʼи в StatefulSet зараз використовують попередній образ контейнера.

Підготовка оновлення

Ви можете розбити оновлення StatefulSet, який використовує стратегію RollingUpdate, на розділи, вказавши .spec.updateStrategy.rollingUpdate.partition.

Для отримання додаткового контексту ви можете прочитати Поточні оновлення частинами на сторінці концепції StatefulSet.

Ви можете підготувати оновлення StatefulSet, використовуючи поле partition всередині .spec.updateStrategy.rollingUpdate. Для цього оновлення ви залишите наявні Podʼи в StatefulSet без змін, поки змінюєте шаблон Podʼа для StatefulSet. Потім ви (або, це поза цим навчальним посібником, якась зовнішня автоматизація) можете запустити це підготовлене оновлення.

Спочатку виправте StatefulSet web, щоб додати розділ до поля updateStrategy:

# Значення "partition" визначає, до яких порядкових номерів застосовується зміна
# Переконайтеся, що використовуєте число, більше, ніж останній порядковий номер для
# StatefulSet
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'
statefulset.apps/web patched

Знову виправте StatefulSet, щоб змінити образ контейнера, який використовує цей StatefulSet:

kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"registry.k8s.io/nginx-slim:0.21"}]'
statefulset.apps/web patched

Видаліть Pod у StatefulSet:

kubectl delete pod web-2
pod "web-2" deleted

Зачекайте, поки замінений Pod web-2 буде запущений і готовий:

# Закінчіть перегляд, коли побачите, що web-2 справний
kubectl get pod -l app=nginx --watch
NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          4m
web-1     1/1       Running             0          4m
web-2     0/1       ContainerCreating   0          11s
web-2     1/1       Running   0         18s

Отримайте образ контейнера Podʼа:

kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
registry.k8s.io/nginx-slim:0.24

Зверніть увагу, що, навіть якщо стратегія оновлення — RollingUpdate, StatefulSet відновив Pod з початковим образом контейнера. Це тому, що порядковий номер Podʼа менше, ніж partition, вказаний у updateStrategy.

Канаркове оновлення

Тепер ви спробуєте канаркове оновлення цієї підготовленої зміни.

Ви можете виконати канаркове оновлення (для тестування зміненого шаблону) шляхом зменшення partition, який ви вказали вище.

Виправте StatefulSet, щоб зменшити розділ:

# Значення "partition" повинно відповідати найвищому існуючому порядковому номеру для
# StatefulSet
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'
statefulset.apps/web patched

Панель управління спровокує заміну для web-2 (реалізовану через належне видалення, а потім створення нового Podʼа, як тільки видалення завершиться). Зачекайте, поки новий Pod web-2 буде запущений і готовий.

# Це вже має бути запущено
kubectl get pod -l app=nginx --watch
NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          4m
web-1     1/1       Running             0          4m
web-2     0/1       ContainerCreating   0          11s
web-2     1/1       Running   0         18s

Отримайте інформацію про контейнер Podʼа:

kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
registry.k8s.io/nginx-slim:0.21

Коли ви змінили partition, контролер StatefulSet автоматично оновив Pod web-2, оскільки порядковий номер Podʼа був більшим або рівним partition.

Видаліть Pod web-1:

kubectl delete pod web-1
pod "web-1" deleted

Зачекайте, поки Pod web-1 буде запущений і готовий.

# Це вже має бути запущено
kubectl get pod -l app=nginx --watch

Вивід подібний до:

NAME      READY     STATUS        RESTARTS   AGE
web-0     1/1       Running       0          6m
web-1     0/1       Terminating   0          6m
web-2     1/1       Running       0          2m
web-1     0/1       Terminating   0         6m
web-1     0/1       Terminating   0         6m
web-1     0/1       Terminating   0         6m
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         18s

Отримайте образ контейнера Podʼа web-1:

kubectl get pod web-1 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
registry.k8s.io/nginx-slim:0.24

web-1 був відновлений до своєї початкової конфігурації, тому що порядковий номер Podʼа був меншим за partition. Коли вказано partition, всі Podʼи з порядковим номером, який більше або дорівнює partition, будуть оновлені, коли буде оновлено .spec.template StatefulSet. Якщо Pod з порядковим номером, який менший за partition, буде видалений або інакше припинений, він буде відновлено до початкової конфігурації.

Поступові оновлення

Ви можете виконати поступове оновлення (наприклад, лінійне, геометричне або експоненціальне оновлення) за допомогою розділеного оновлення з аналогічним методом до того, як ви впровадили канаркове оновлення. Щоб виконати поступове оновлення, встановіть partition на порядковий номер, на якому ви хочете, щоб контролер призупинив оновлення.

Розділ в цей момент встановлено на 2. Встановіть розділ на 0:

kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'
statefulset.apps/web patched

Зачекайте, поки всі Podʼи в StatefulSet стануть запущеними і готовими.

# Це вже має бути запущено
kubectl get pod -l app=nginx --watch

Вивід подібний до:

NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          3m
web-1     0/1       ContainerCreating   0          11s
web-2     1/1       Running             0          2m
web-1     1/1       Running   0         18s
web-0     1/1       Terminating   0         3m
web-0     1/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Pending   0         0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         3s

Отримайте деталі образу контейнера для Podʼів у StatefulSet:

for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
registry.k8s.io/nginx-slim:0.21
registry.k8s.io/nginx-slim:0.21
registry.k8s.io/nginx-slim:0.21

Переміщуючи partition на 0, ви дозволили StatefulSet продовжити процес оновлення.

OnDelete

Ви обираєте цю стратегію оновлення для StatefulSet, встановивши .spec.template.updateStrategy.type на OnDelete.

Виправте StatefulSet web, щоб використовувати стратегію оновлення OnDelete:

kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"OnDelete"}}}'
statefulset.apps/web patched

Коли ви обираєте цю стратегію оновлення, контролер StatefulSet не автоматично оновлює Podʼи, коли вноситься зміна до поля .spec.template StatefulSet. Вам потрібно керувати оновленням самостійно — або вручну, або за допомогою окремої автоматизації.

Видалення StatefulSet

StatefulSet підтримує як не каскадне, так і каскадне видалення. При не каскадному видаленні Podʼи StatefulSet не видаляються при видаленні самого StatefulSet. При каскадному видаленні видаляються як StatefulSet, так і його Podʼи.

Прочитайте Використання каскадного видалення у кластері, щоб дізнатися про каскадне видалення загалом.

Некаскадне видалення

В одному вікні термінала спостерігайте за Podʼами у StatefulSet.

# Завершіть цей перегляд, коли не буде Podʼів для StatefulSet
kubectl get pods --watch -l app=nginx

Використовуйте kubectl delete, щоб видалити StatefulSet. Переконайтеся, що ви передали параметр --cascade=orphan до команди. Цей параметр повідомляє Kubernetes видаляти лише StatefulSet, і не видаляти жодного з його Podʼів.

kubectl delete statefulset web --cascade=orphan
statefulset.apps "web" deleted

Отримайте Podʼи, щоб перевірити їх статус:

kubectl get pods -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          6m
web-1     1/1       Running   0          7m
web-2     1/1       Running   0          5m

Навіть якщо web був видалений, всі Podʼи все ще запущені і готові. Видаліть web-0:

kubectl delete pod web-0
pod "web-0" deleted

Отримайте Podʼи StatefulSet:

kubectl get pods -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-1     1/1       Running   0          10m
web-2     1/1       Running   0          7m

Оскільки StatefulSet web було видалено, web-0 не було перезапущено.

В одному терміналі спостерігайте за Podʼами StatefulSet.

# Залиште цей перегляд запущеним до наступного разу, коли ви почнете перегляд
kubectl get pods --watch -l app=nginx

У другому терміналі створіть знову StatefulSet. Зверніть увагу, якщо ви не видалили nginx Service (що ви не повинні були робити), ви побачите помилку, яка вказує, що Service вже існує.

kubectl apply -f https://k8s.io/examples/application/web/web.yaml
statefulset.apps/web created
service/nginx unchanged

Ігноруйте помилку. Вона лише вказує на те, що була спроба створити headless Service nginx, незважаючи на те, що цей Service вже існує.

Дослідіть вихідні дані команди kubectl get, яка працює у першому терміналі.

# Це вже має бути запущено
kubectl get pods --watch -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-1     1/1       Running   0          16m
web-2     1/1       Running   0          2m
NAME      READY     STATUS    RESTARTS   AGE
web-0     0/1       Pending   0          0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         18s
web-2     1/1       Terminating   0         3m
web-2     0/1       Terminating   0         3m
web-2     0/1       Terminating   0         3m
web-2     0/1       Terminating   0         3m

Коли StatefulSet web був відновлений, він спочатку перезапустив web-0. Оскільки web-1 вже був запущений і готовий, коли web-0 перейшов до стану Running і Ready, він став резервним для цього Podʼа. Оскільки ви відновили StatefulSet з replicas рівним 2, як тільки web-0 був відновлений, і як тільки web-1 вже був визначений як Running і Ready, web-2 був закінчений.

Тепер ще раз розгляньте вміст файлу index.html, який обслуговують вебсервери Podʼів:

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

Навіть якщо ви видалили як StatefulSet, так і Pod web-0, він все ще обслуговує імʼя хоста, яке спочатку було введено в його файл index.html. Це тому, що StatefulSet ніколи не видаляє PersistentVolumes, повʼязані з Podʼом. Коли ви відновили StatefulSet і перезапустили web-0, його оригінальний PersistentVolume знову змонтувався.

Каскадне видалення

В одному вікні термінала спостерігайте за Podʼами у StatefulSet.

# Залиште це запущеним до наступного розділу сторінки
kubectl get pods --watch -l app=nginx

В іншому терміналі знову видаліть StatefulSet. Цього разу не використовуйте параметр --cascade=orphan.

kubectl delete statefulset web
statefulset.apps "web" deleted

Дослідіть вихідні дані команди kubectl get, яка працює у першому терміналі, і зачекайте, поки всі Podʼи перейдуть у стан Terminating.

# Це вже має бути запущено
kubectl get pods --watch -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          11m
web-1     1/1       Running   0          27m
NAME      READY     STATUS        RESTARTS   AGE
web-0     1/1       Terminating   0          12m
web-1     1/1       Terminating   0         29m
web-0     0/1       Terminating   0         12m
web-0     0/1       Terminating   0         12m
web-0     0/1       Terminating   0         12m
web-1     0/1       Terminating   0         29m
web-1     0/1       Terminating   0         29m
web-1     0/1       Terminating   0         29m

Як ви бачили в розділі Зменшення мастабу, Podʼи видаляються по одному, з урахуванням зворотного порядку їх порядкових індексів. Перед видаленням Podʼа контролер StatefulSet чекає, поки Pod-наступник буде повністю видалено.

kubectl delete service nginx
service "nginx" deleted

Знову створіть StatefulSet і headless Service:

kubectl apply -f https://k8s.io/examples/application/web/web.yaml
service/nginx created
statefulset.apps/web created

Коли всі Podʼи StatefulSet перейдуть у стан Running і Ready, отримайте вміст їх файлів index.html:

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

Навіть якщо ви повністю видалили StatefulSet і всі його Podʼи, Podʼи перестворюються з монтуванням їх PersistentVolumes, і web-0 й web-1 продовжують обслуговувати свої імена хостів.

Нарешті, видаліть Service nginx

kubectl delete service nginx
service "nginx" deleted

…і StatefulSet web:

kubectl delete statefulset web
statefulset "web" deleted

Політика управління Podʼами

Для деяких розподілених систем порядок, гарантований StatefulSet, є непотрібним або небажаним. Ці системи потребують лише унікальності та ідентичності.

Ви можете вказати політику управління Podʼами, щоб уникнути цього строгого порядку; або OrderedReady (типово), або Parallel.

Політика управління Podʼами OrderedReady

Управління Podʼами OrderedReady є стандартним значенням для StatefulSet. Воно каже контролеру StatefulSet дотримуватися гарантій порядку, показаних вище.

Використовуйте цей параметр, коли ваш застосунок вимагає або очікує, що зміни, такі як запуск нової версії вашого застосунку, відбуваються у строгому порядку за порядковим номером (номером Pod), який надає StatefulSet. Іншими словами, якщо у вас є Podʼи app-0, app-1 та app-2, Kubernetes оновить app-0 першим і перевірить його. Якщо перевірка пройшла успішно, Kubernetes оновить app-1, а потім app-2.

Якщо ви додали ще два Podʼи, Kubernetes налаштує app-3 та почекає, доки він не стане справним, перед тим як розгорнути app-4.

Оскільки це стандартне налаштування, ви вже практикували його використання.

Політика управління Podʼами Parallel

Альтернатива, управління Podʼами Parallel, каже контролеру StatefulSet запускати або видаляти всі Podʼи паралельно, і не чекати, поки Podʼи не стануть Running і Ready або повністю видаленими перед запуском або видаленням іншого Podʼа.

Опція управління Podʼами Parallel впливає лише на поведінку при масштабуванні. Оновлення не піддаються впливу; Kubernetes все ще впроваджує зміни по черзі. Для цього навчального посібника застосунок дуже простий: вебсервер, який повідомляє вам його імʼя хосту (тому що це StatefulSet, імʼя хосту для кожного Podʼа є різним і передбачуваним).

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  podManagementPolicy: "Parallel"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: registry.k8s.io/nginx-slim:0.24
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

Цей маніфест ідентичний тому, який ви завантажили вище, за винятком того, що .spec.podManagementPolicy StatefulSet web встановлено на Parallel.

В одному терміналі спостерігайте за Podʼами в StatefulSet.

# Залиште це запущеним до кінця розділу
kubectl get pod -l app=nginx --watch

В іншому терміналі змініть конфігурацію StatefulSet на управління Podʼами Parallel:

kubectl apply -f https://k8s.io/examples/application/web/web-parallel.yaml
service/nginx updated
statefulset.apps/web updated

Залиште відкритим термінал, де ви запустили спостереження. У іншому вікні терміналу масштабуйте StatefulSet:

kubectl scale statefulset/web --replicas=5
statefulset.apps/web scaled

Дослідіть вихідні дані терміналу, де запущена команда kubectl get. Вони можуть виглядати приблизно так

web-3     0/1       Pending   0         0s
web-3     0/1       Pending   0         0s
web-3     0/1       Pending   0         7s
web-3     0/1       ContainerCreating   0         7s
web-2     0/1       Pending   0         0s
web-4     0/1       Pending   0         0s
web-2     1/1       Running   0         8s
web-4     0/1       ContainerCreating   0         4s
web-3     1/1       Running   0         26s
web-4     1/1       Running   0         2s

StatefulSet запустив три нові Podʼи, і він не чекав, поки перший стане Running і Ready перед запуском другого і третього Podʼів.

Цей підхід є корисним, якщо ваше робоче навантаження зберігає стан або Podʼи мають можливість ідентифікувати один одного за передбачуваною назвою, особливо якщо вам іноді потрібно швидко надати більше потужності. Якщо цей простий вебсервіс для навчального посібника раптово отримав додаткові 1,000,000 запитів на хвилину, то ви хотіли б запустити додаткові Podʼи — але ви також не хотіли б чекати, поки кожен новий Pod буде запущений. Запуск додаткових Podʼів паралельно скорочує час між запитом на додаткову потужність і її доступністю для використання.

Очищення

У вас маєть бути два термінали, готові для запуску команд kubectl для проведення очищення.

kubectl delete sts web
# sts - скорочення для statefulset

Ви можете відстежувати виконання команди kubectl get, щоб переглянути видалення цих ресурсів.

# завершіть перегляд, коли побачите необхідне
kubectl get pod -l app=nginx --watch
web-3     1/1       Terminating   0         9m
web-2     1/1       Terminating   0         9m
web-3     1/1       Terminating   0         9m
web-2     1/1       Terminating   0         9m
web-1     1/1       Terminating   0         44m
web-0     1/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-3     0/1       Terminating   0         9m
web-2     0/1       Terminating   0         9m
web-1     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-2     0/1       Terminating   0         9m
web-2     0/1       Terminating   0         9m
web-2     0/1       Terminating   0         9m
web-1     0/1       Terminating   0         44m
web-1     0/1       Terminating   0         44m
web-1     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-3     0/1       Terminating   0         9m
web-3     0/1       Terminating   0         9m
web-3     0/1       Terminating   0         9m

Під час видалення StatefulSet видаляються всі Podʼи одночасно; не чекаючи завершення роботи наступного Podʼа у черзі.

Закрийте термінал, де виконується команда kubectl get, та видаліть Service nginx:

kubectl delete svc nginx

Видаліть носій постійного зберігання для PersistentVolumes, що використовувалися в цьому посібнику.

kubectl get pvc
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0   Bound    pvc-2bf00408-d366-4a12-bad0-1869c65d0bee   1Gi        RWO            standard       25m
www-web-1   Bound    pvc-ba3bfe9c-413e-4b95-a2c0-3ea8a54dbab4   1Gi        RWO            standard       24m
www-web-2   Bound    pvc-cba6cfa6-3a47-486b-a138-db5930207eaf   1Gi        RWO            standard       15m
www-web-3   Bound    pvc-0c04d7f0-787a-4977-8da3-d9d3a6d8d752   1Gi        RWO            standard       15m
www-web-4   Bound    pvc-b2c73489-e70b-4a4e-9ec1-9eab439aa43e   1Gi        RWO            standard       14m
kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
pvc-0c04d7f0-787a-4977-8da3-d9d3a6d8d752   1Gi        RWO            Delete           Bound    default/www-web-3   standard                15m
pvc-2bf00408-d366-4a12-bad0-1869c65d0bee   1Gi        RWO            Delete           Bound    default/www-web-0   standard                25m
pvc-b2c73489-e70b-4a4e-9ec1-9eab439aa43e   1Gi        RWO            Delete           Bound    default/www-web-4   standard                14m
pvc-ba3bfe9c-413e-4b95-a2c0-3ea8a54dbab4   1Gi        RWO            Delete           Bound    default/www-web-1   standard                24m
pvc-cba6cfa6-3a47-486b-a138-db5930207eaf   1Gi        RWO            Delete           Bound    default/www-web-2   standard                15m
kubectl delete pvc www-web-0 www-web-1 www-web-2 www-web-3 www-web-4
persistentvolumeclaim "www-web-0" deleted
persistentvolumeclaim "www-web-1" deleted
persistentvolumeclaim "www-web-2" deleted
persistentvolumeclaim "www-web-3" deleted
persistentvolumeclaim "www-web-4" deleted
kubectl get pvc
No resources found in default namespace.

6.2 - Приклад: Розгортання WordPress та MySQL з постійними томами

У цьому посібнику ви дізнаєтеся, як розгорнути сайт WordPress та базу даних MySQL за допомогою Minikube. Обидва застосунки використовують PersistentVolumes та PersistentVolumeClaims для зберігання даних.

Постійні томи (PersistentVolume (PV)) — це частина системи зберігання в кластері, яку адміністратор вручну надав або яку Kubernetes автоматично надав за допомогою StorageClass. Запити на постійні томи (PersistentVolumeClaim (PVC)) — це запит на зберігання, який користувач може отримати через PV. PersistentVolumes та PersistentVolumeClaims незалежні від життєвого циклу Podʼів і зберігають дані під час перезапуску, перепланування та навіть видалення Podʼів.

Цілі

  • Створення PersistentVolumeClaims and PersistentVolumes
  • Створення kustomization.yaml з
    • генератором Secret
    • конфігураціями ресурсів MySQL
    • конфігураціями ресурсів WordPress
  • Застосування теки kustomization за допомогою kubectl apply -k ./
  • Очищення

Перш ніж ви розпочнете

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Для перевірки версії введіть kubectl version.

Наведений на цій сторінці приклад працює з kubectl версії 1.27 і вище.

Завантажте наступні конфігураційні файли:

  1. mysql-deployment.yaml

  2. wordpress-deployment.yaml

Створення запитів на постійні томи та постійних томів

Для зберігання даних MySQL та Wordpress кожному потрібен постійний том (PersistentVolume). Їх запити на постійні томи (PersistentVolumeClaim) будуть створені на етапі розгортання.

У багатьох середовищах кластера встановлено типовий StorageClass. Якщо StorageClass не вказано у запиті на постійний том (PersistentVolumeClaim), то використовується типовий StorageClass кластера.

Коли створюється запит на постійний том (PersistentVolumeClaim), постійний том (PersistentVolume) динамічно надається на основі конфігурації StorageClass.

Створення файлу kustomization.yaml

Додавання генератора Secret

Secret — це обʼєкт, який зберігає чутливі дані, такі як паролі або ключі. Починаючи з версії 1.14, kubectl підтримує керування обʼєктами Kubernetes за допомогою файлу kustomization. Ви можете створити Secret за допомогою генераторів у файлі kustomization.yaml.

Додайте генератор Secret у файл kustomization.yaml за допомогою наступної команди. Вам потрібно буде замінити YOUR_PASSWORD на пароль, який ви хочете використовувати.

cat <<EOF >./kustomization.yaml
secretGenerator:
- name: mysql-pass
  literals:
  - password=YOUR_PASSWORD
EOF

Додавання конфігурації ресурсів для MySQL та WordPress

Наступний маніфест описує Deployment одного екземпляра MySQL. Контейнер MySQL монтує PersistentVolume у /var/lib/mysql. Змінна оточення MYSQL_ROOT_PASSWORD встановлює пароль бази даних з Secret.

apiVersion: v1
kind: Service
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  ports:
    - port: 3306
  selector:
    app: wordpress
    tier: mysql
  clusterIP: None
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: mysql
    spec:
      containers:
      - image: mysql:8.0
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        - name: MYSQL_DATABASE
          value: wordpress
        - name: MYSQL_USER
          value: wordpress
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim

Наступний маніфест описує Deployment одного екземпляра WordPress. Контейнер WordPress монтує PersistentVolume у /var/www/html для файлів даних вебсайту. Змінна оточення WORDPRESS_DB_HOST встановлює імʼя служби MySQL, визначеної вище, і WordPress буде звертатися до бази даних через службу. Змінна оточення WORDPRESS_DB_PASSWORD встановлює пароль бази даних згенерованого kustomize Secret.

apiVersion: v1
kind: Service
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  ports:
    - port: 80
  selector:
    app: wordpress
    tier: frontend
  type: LoadBalancer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wp-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: frontend
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: frontend
    spec:
      containers:
      - image: wordpress:6.2.1-apache
        name: wordpress
        env:
        - name: WORDPRESS_DB_HOST
          value: wordpress-mysql
        - name: WORDPRESS_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        - name: WORDPRESS_DB_USER
          value: wordpress
        ports:
        - containerPort: 80
          name: wordpress
        volumeMounts:
        - name: wordpress-persistent-storage
          mountPath: /var/www/html
      volumes:
      - name: wordpress-persistent-storage
        persistentVolumeClaim:
          claimName: wp-pv-claim
  1. Завантажте файл конфігурації розгортання MySQL.

    curl -LO https://k8s.io/examples/application/wordpress/mysql-deployment.yaml
    
  2. Завантажте файл конфігурації розгортання WordPress.

    curl -LO https://k8s.io/examples/application/wordpress/wordpress-deployment.yaml
    
  3. Додайте їх до файлу kustomization.yaml.

    cat <<EOF >>./kustomization.yaml
    resources:
      - mysql-deployment.yaml
      - wordpress-deployment.yaml
    EOF
    

Застосування та перевірка

Файл kustomization.yaml містить всі ресурси для розгортання сайту WordPress та бази даних MySQL. Ви можете застосувати цю теку командою

kubectl apply -k ./

Тепер перевірте, що всі обʼєкти існують.

  1. Перевірте, що Secret існує, виконавши наступну команду:

    kubectl get secrets
    

    Відповідь має бути подібною до цієї:

    NAME                    TYPE                                  DATA   AGE
    mysql-pass-c57bb4t7mf   Opaque                                1      9s
    
  2. Перевірте, що PersistentVolume був динамічно наданий.

    kubectl get pvc
    

    Відповідь має бути подібною до цієї:

    NAME             STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS       AGE
    mysql-pv-claim   Bound     pvc-8cbd7b2e-4044-11e9-b2bb-42010a800002   20Gi       RWO            standard           77s
    wp-pv-claim      Bound     pvc-8cd0df54-4044-11e9-b2bb-42010a800002   20Gi       RWO            standard           77s
    
  3. Перевірте, що Pod працює, виконавши наступну команду:

    kubectl get pods
    

    Відповідь має бути подібною до цієї:

    NAME                               READY     STATUS    RESTARTS   AGE
    wordpress-mysql-1894417608-x5dzt   1/1       Running   0          40s
    
  4. Перевірте, що Service працює, виконавши наступну команду:

    kubectl get services wordpress
    

    Відповідь має бути подібною до цієї:

    NAME        TYPE            CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
    wordpress   LoadBalancer    10.0.0.89    <pending>     80:32406/TCP   4m
    
  5. Виконайте наступну команду, щоб отримати IP-адресу для Service WordPress:

    minikube service wordpress --url
    

    Відповідь має бути подібною до цієї:

    http://1.2.3.4:32406
    
  6. Скопіюйте IP-адресу та завантажте сторінку у своєму оглядачі, щоб переглянути ваш сайт.

    Ви повинні побачити сторінку налаштування WordPress, схожу на знімок екрана нижче.

    wordpress-init

Очищення

  1. Виконайте наступну команду, щоб видалити ваш Secret, Deployments, Services та PersistentVolumeClaims:

    kubectl delete -k ./
    

Що далі

6.3 - Приклад: Розгортання Cassandra з використанням StatefulSet

Цей підручник покаже вам, як запустити Apache Cassandra у Kubernetes. Cassandra, база даних, потребує постійного сховища для забезпечення стійкості даних (стан застосунку). У цьому прикладі використовується власний постачальник насіння Cassandra, що дозволяє базі даних виявляти нові екземпляри Cassandra, коли вони приєднуються до кластера Cassandra.

StatefulSet полегшує розгортання стійких застосунків у вашому кластері Kubernetes. Для отримання додаткової інформації про використані у цьому підручнику функції дивіться StatefulSet.

Цілі

  • Створення та перевірка Cassandra headless Service.
  • Використання StatefulSet для створення кільця Cassandra.
  • Перевірка StatefulSet.
  • Зміна StatefulSet.
  • Видалення StatefulSet та його Podʼів.

Перш ніж ви розпочнете

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

Для роботи з цим посібником ви повинні вже мати базові знання про Podʼи, Сервіси, та StatefulSets.

Додаткові інструкції щодо налаштування Minikube

Створення headless Service для Cassandra

У Kubernetes Service описує набір Podʼів, які виконують одну й ту ж задачу.

Наступний Service використовується для DNS-пошуку між Podʼами Cassandra та клієнтами у вашому кластері:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: cassandra
  name: cassandra
spec:
  clusterIP: None
  ports:
  - port: 9042
  selector:
    app: cassandra

Створіть Service для відстеження всіх членів StatefulSet Cassandra з файлу cassandra-service.yaml:

kubectl apply -f https://k8s.io/examples/application/cassandra/cassandra-service.yaml

Перевірка (необов’язково)

Отримайте інформацію про Cassandra Service.

kubectl get svc cassandra

Відповідь буде

NAME        TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
cassandra   ClusterIP   None         <none>        9042/TCP   45s

Якщо ви не бачите Service з назвою cassandra, це означає, що його створення не вдалося. Прочитайте Налагодження Service, щоб отримати довідку з усунення загальних проблем.

Використання StatefulSet для створення кільця Cassandra

Маніфест StatefulSet, наведений нижче, створює кільце Cassandra, що складається з трьох Podʼів.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: cassandra
  labels:
    app: cassandra
spec:
  serviceName: cassandra
  replicas: 3
  selector:
    matchLabels:
      app: cassandra
  template:
    metadata:
      labels:
        app: cassandra
    spec:
      terminationGracePeriodSeconds: 500
      containers:
      - name: cassandra
        image: gcr.io/google-samples/cassandra:v13
        imagePullPolicy: Always
        ports:
        - containerPort: 7000
          name: intra-node
        - containerPort: 7001
          name: tls-intra-node
        - containerPort: 7199
          name: jmx
        - containerPort: 9042
          name: cql
        resources:
          limits:
            cpu: "500m"
            memory: 1Gi
          requests:
            cpu: "500m"
            memory: 1Gi
        securityContext:
          capabilities:
            add:
              - IPC_LOCK
        lifecycle:
          preStop:
            exec:
              command: 
              - /bin/sh
              - -c
              - nodetool drain
        env:
          - name: MAX_HEAP_SIZE
            value: 512M
          - name: HEAP_NEWSIZE
            value: 100M
          - name: CASSANDRA_SEEDS
            value: "cassandra-0.cassandra.default.svc.cluster.local"
          - name: CASSANDRA_CLUSTER_NAME
            value: "K8Demo"
          - name: CASSANDRA_DC
            value: "DC1-K8Demo"
          - name: CASSANDRA_RACK
            value: "Rack1-K8Demo"
          - name: POD_IP
            valueFrom:
              fieldRef:
                fieldPath: status.podIP
        readinessProbe:
          exec:
            command:
            - /bin/bash
            - -c
            - /ready-probe.sh
          initialDelaySeconds: 15
          timeoutSeconds: 5
        # Ці точки монтування томів є постійними. Вони подібни до вбудованих заявок,
        # але не зовсіс, тому що імена повинні точно збігатись з одним з томів
        # томів Podʼів stateful.
        volumeMounts:
        - name: cassandra-data
          mountPath: /cassandra_data
  # Це перетворюється у заявки на томи контролером
  # та монтується в шлях, зазначений вище.
  # не використовуйте це в операційній діяльності, допоки тип ssd є GCEPersistentDisk чи інший ssd pd
  volumeClaimTemplates:
  - metadata:
      name: cassandra-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: fast
      resources:
        requests:
          storage: 1Gi
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: fast
provisioner: k8s.io/minikube-hostpath
parameters:
  type: pd-ssd

Створіть StatefulSet Cassandra з файлу cassandra-statefulset.yaml:

# Використовуйте це, якщо ви можете застосувати cassandra-statefulset.yaml без змін
kubectl apply -f https://k8s.io/examples/application/cassandra/cassandra-statefulset.yaml

Якщо вам потрібно змінити cassandra-statefulset.yaml для вашого кластера, завантажте https://k8s.io/examples/application/cassandra/cassandra-statefulset.yaml та застосуйте цей маніфест, з теки, в яку ви зберегли змінену версію:

# Використовуйте це, якщо ви змінили cassandra-statefulset.yaml локально
kubectl apply -f cassandra-statefulset.yaml

Перевірка StatefulSet Cassandra

  1. Отримайте StatefulSet Cassandra:

    kubectl get statefulset cassandra
    

    Відповідь буде схожа на:

    NAME        DESIRED   CURRENT   AGE
    cassandra   3         0         13s
    

    Ресурс StatefulSet розгортає Podʼи послідовно.

  2. Отримайте Podʼи, щоб побачити статус створення в зазначеному порядку:

    kubectl get pods -l="app=cassandra"
    

    Відповідь буде схожа на:

    NAME          READY     STATUS              RESTARTS   AGE
    cassandra-0   1/1       Running             0          1m
    cassandra-1   0/1       ContainerCreating   0          8s
    

    На створення всіх трьох Podʼів може піти декілька хвилин. Після їх розгортання ця ж команда повертає вихідні дані, схожі на:

    NAME          READY     STATUS    RESTARTS   AGE
    cassandra-0   1/1       Running   0          10m
    cassandra-1   1/1       Running   0          9m
    cassandra-2   1/1       Running   0          8m
    
  3. Виконайте інструмент nodetool Cassandra всередині першого Podʼа, щоб переглянути стан кільця.

    kubectl exec -it cassandra-0 -- nodetool status
    

    Відповідь буде схожою на:

    Datacenter: DC1-K8Demo
    ======================
    Status=Up/Down
    |/ State=Normal/Leaving/Joining/Moving
    --  Address     Load       Tokens       Owns (effective)  Host ID                               Rack
    UN  172.17.0.5  83.57 KiB  32           74.0%             e2dd09e6-d9d3-477e-96c5-45094c08db0f  Rack1-K8Demo
    UN  172.17.0.4  101.04 KiB  32           58.8%             f89d6835-3a42-4419-92b3-0e62cae1479c  Rack1-K8Demo
    UN  172.17.0.6  84.74 KiB  32           67.1%             a6a1e8c2-3dc5-4417-b1a0-26507af2aaad  Rack1-K8Demo
    

Зміна StatefulSet Cassandra

Використовуйте kubectl edit, щоб змінити розмір StatefulSet Cassandra.

  1. Виконайте наступну команду:

    kubectl edit statefulset cassandra
    

    Ця команда відкриває редактор у вашому терміналі. Рядок, який вам потрібно змінити, — це поле replicas. Наведений нижче приклад — це уривок з файлу StatefulSet:

    # Будь ласка, відредагуйте об’єкт нижче. Рядки, що починаються з '#', будуть проігноровані,
    # і пустий файл призведе до відмови редагування. Якщо під час збереження файлу виникне помилка,
    # цей файл буде знову відкритий із відповідними несправностями.
    #
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      creationTimestamp: 2016-08-13T18:40:58Z
    
    
      generation: 1
      labels:
      app: cassandra
      name: cassandra
      namespace: default
      resourceVersion: "323"
      uid: 7a219483-6185-11e6-a910-42010a8a0fc0
    spec:
      replicas: 3
    
  2. Змініть кількість реплік на 4 і збережіть маніфест.

    Тепер StatefulSet масштабується для роботи з 4 Podʼами.

  3. Отримайте StatefulSet Cassandra, щоб перевірити зміни:

    kubectl get statefulset cassandra
    

    Відповідь буде схожа на:

    NAME        DESIRED   CURRENT   AGE
    cassandra   4         4         36m
    

Очищення

Видалення або зменшення масштабу StatefulSet не призводить до видалення томів, повʼязаних із StatefulSet. Це налаштування захищає вас, оскільки ваші дані цінніші, ніж автоматичне очищення всіх повʼязаних ресурсів StatefulSet.

  1. Виконайте наступні команди (послідовно обʼєднані в одну команду) для видалення всього в StatefulSet Cassandra:

    grace=$(kubectl get pod cassandra-0 -o=jsonpath='{.spec.terminationGracePeriodSeconds}') \
      && kubectl delete statefulset -l app=cassandra \
      && echo "Sleeping ${grace} seconds" 1>&2 \
      && sleep $grace \
      && kubectl delete persistentvolumeclaim -l app=cassandra
    
  2. Виконайте наступну команду для видалення Service, який ви налаштували для Cassandra:

    kubectl delete service -l app=cassandra
    

Змінні середовища контейнера Cassandra

Podʼи в цьому посібнику використовують образ gcr.io/google-samples/cassandra:v13 з реєстру контейнерів Google. Докер-образ вище базується на debian-base і містить OpenJDK 8.

Цей образ включає стандартну установку Cassandra з репозиторію Apache Debian. За допомогою змінних середовища ви можете змінити значення, які вставляються в cassandra.yaml.

Змінна середовищаСтандартне значенняанням
CASSANDRA_CLUSTER_NAME'Test Cluster'
CASSANDRA_NUM_TOKENS32
CASSANDRA_RPC_ADDRESS0.0.0.0

Що далі

6.4 - Запуск ZooKeeper, розподіленого системного координатора

Цей посібник демонструє як запускати Apache Zookeeper в Kubernetes, використовуючи StatefulSets, PodDisruptionBudgets, та PodAntiAffinity.

Перш ніж ви розпочнете

Перед тим як розпочати, переконайтеся, що ви маєте уявлення про:

Вам необхідно мати кластер із щонайменше чотирма вузлами, і кожен вузол повинен мати щонайменше 2 ЦП та 4 ГБ памʼяті. У цьому посібнику ви будете закривати (cordon) та очищувати для обслуговування (drain) вузли кластера. Це означає, що кластер припинить роботу та виселить всі Podʼи зі своїх вузлів, і вузли тимчасово стануть не придатними до розміщення Podʼів. Вам слід використовувати окремий кластер для цього посібника, або ви повинні забезпечити, що порушення, яке ви викличете, не нашкодить іншим мешканцям кластера.

У цьому посібнику передбачається, що ви налаштували свій кластер для динамічного надання постійних томів (PersistentVolumes). Якщо ваш кластер не налаштований на це, вам доведеться вручну створити три томи обсягом 20 ГБ перед початком виконання кроків цього посібника.

Цілі

Після проходження цього посібника ви будете знати, як:

  • Розгортати ансамбль Apache Zookeeper використовуючи StatefulSet.
  • Як надійно налаштовувати ансамбль.
  • Як поширювати розгортання серверів Zookeeper в ансамблі.
  • Як використовувати PodDisruptionBudgets для забезпечення високої доступності послуг під час запланованого обслуговування.

ZooKeeper

Apache ZooKeeper — це розподілена, відкрита координаційна служба для розподілених застосунків. ZooKeeper дозволяє читати, записувати та спостерігати за оновленнями даних. Дані організовані у вигляді ієрархії файлової системи та реплікуються на всі сервери ZooKeeper в ансамблі (набір серверів ZooKeeper). Всі операції з даними є атомарними та послідовно консистентними. ZooKeeper забезпечує це, використовуючи протокол консенсусу Zab для реплікації машини стану на всіх серверах в ансамблі.

Ансамбль використовує протокол Zab для вибору лідера, ансамбль не може записувати дані, поки цей вибір лідера не завершиться. Після його завершення ансамбль використовує Zab, щоб забезпечити реплікацію всіх записів до кворуму, перш ніж він підтверджує їх та робить їх видимими для клієнтів. Без зваженого кворуму, кворум — буде більшістю складових ансамблю, які містять поточного лідера. Наприклад, якщо в ансамблі є три сервери, компонент, який містить лідера і ще один сервер, становитимуть кворум. Якщо ансамбль не може досягти кворуму, він не може записувати дані.

Сервери ZooKeeper зберігають свою повну машину стану в памʼяті та записують кожну зміну до довгострокового WAL (Write Ahead Log) на носії інформації. Коли сервер аварійно завершує роботу, він може відновити свій попередній стан, відтворюючи WAL. Щоб запобігти безмежному зростанню WAL, сервери ZooKeeper періодично роблять знімки їхнього стану в памʼяті на носій інформації. Ці знімки можуть бути завантажені безпосередньо в памʼять, а всі записи WAL, які передували знімку, можуть бути видалені.

Створення ансамблю ZooKeeper

Маніфест нижче містить:

apiVersion: v1
kind: Service
metadata:
  name: zk-hs
  labels:
    app: zk
spec:
  ports:
  - port: 2888
    name: server
  - port: 3888
    name: leader-election
  clusterIP: None
  selector:
    app: zk
---
apiVersion: v1
kind: Service
metadata:
  name: zk-cs
  labels:
    app: zk
spec:
  ports:
  - port: 2181
    name: client
  selector:
    app: zk
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  selector:
    matchLabels:
      app: zk
  maxUnavailable: 1
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: zk
spec:
  selector:
    matchLabels:
      app: zk
  serviceName: zk-hs
  replicas: 3
  updateStrategy:
    type: RollingUpdate
  podManagementPolicy: OrderedReady
  template:
    metadata:
      labels:
        app: zk
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: "app"
                    operator: In
                    values:
                    - zk
              topologyKey: "kubernetes.io/hostname"
      containers:
      - name: kubernetes-zookeeper
        imagePullPolicy: Always
        image: "registry.k8s.io/kubernetes-zookeeper:1.0-3.4.10"
        resources:
          requests:
            memory: "1Gi"
            cpu: "0.5"
        ports:
        - containerPort: 2181
          name: client
        - containerPort: 2888
          name: server
        - containerPort: 3888
          name: leader-election
        command:
        - sh
        - -c
        - "start-zookeeper \
          --servers=3 \
          --data_dir=/var/lib/zookeeper/data \
          --data_log_dir=/var/lib/zookeeper/data/log \
          --conf_dir=/opt/zookeeper/conf \
          --client_port=2181 \
          --election_port=3888 \
          --server_port=2888 \
          --tick_time=2000 \
          --init_limit=10 \
          --sync_limit=5 \
          --heap=512M \
          --max_client_cnxns=60 \
          --snap_retain_count=3 \
          --purge_interval=12 \
          --max_session_timeout=40000 \
          --min_session_timeout=4000 \
          --log_level=INFO"
        readinessProbe:
          exec:
            command:
            - sh
            - -c
            - "zookeeper-ready 2181"
          initialDelaySeconds: 10
          timeoutSeconds: 5
        livenessProbe:
          exec:
            command:
            - sh
            - -c
            - "zookeeper-ready 2181"
          initialDelaySeconds: 10
          timeoutSeconds: 5
        volumeMounts:
        - name: datadir
          mountPath: /var/lib/zookeeper
      securityContext:
        runAsUser: 1000
        fsGroup: 1000
  volumeClaimTemplates:
  - metadata:
      name: datadir
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 10Gi

Відкрийте термінал і скористайтеся командою kubectl apply, щоб створити маніфест.

kubectl apply -f https://k8s.io/examples/application/zookeeper/zookeeper.yaml

Це створить zk-hs Headless Service, zk-cs Service, zk-pdb PodDisruptionBudget та zk StatefulSet.

service/zk-hs створено
service/zk-cs створено
poddisruptionbudget.policy/zk-pdb створено
statefulset.apps/zk створено

Скористайтеся kubectl get, щоб спостерігати за створенням StatefulSet контролером Podʼів StatefulSet.

kubectl get pods -w -l app=zk

Коли Pod zk-2 в стані Running та Ready, скористайтеся CTRL-C, щоб припинити виконання kubectl.

NAME      READY     STATUS    RESTARTS   AGE
zk-0      0/1       Pending   0          0s
zk-0      0/1       Pending   0         0s
zk-0      0/1       ContainerCreating   0         0s
zk-0      0/1       Running   0         19s
zk-0      1/1       Running   0         40s
zk-1      0/1       Pending   0         0s
zk-1      0/1       Pending   0         0s
zk-1      0/1       ContainerCreating   0         0s
zk-1      0/1       Running   0         18s
zk-1      1/1       Running   0         40s
zk-2      0/1       Pending   0         0s
zk-2      0/1       Pending   0         0s
zk-2      0/1       ContainerCreating   0         0s
zk-2      0/1       Running   0         19s
zk-2      1/1       Running   0         40s

Контролер StatefulSet створює три Podʼи, і кожен Pod містить контейнер з сервером ZooKeeper.

Забезпечення виборів лідера

Оскільки в анонімній мережі немає алгоритму завершення для вибору лідера, Zab вимагає явної конфігурації членства для виконання виборів лідера. Кожен сервер в ансамблі повинен мати унікальний ідентифікатор, всі сервери повинні знати глобальний набір ідентифікаторів, і кожен ідентифікатор повинен бути повʼязаний з мережевою адресою.

Використовуйте kubectl exec, щоб отримати імена хостів Podʼів у StatefulSet zk.

for i in 0 1 2; do kubectl exec zk-$i -- hostname; done

Контролер StatefulSet надає кожному Podʼу унікальне імʼя хосту на основі його порядкового індексу. Імена хостів мають форму <імʼя statefulset>-<порядковий індекс>. Оскільки поле replicas StatefulSet zk встановлено на 3, контролер набору створює три Podʼа з іменами хостів zk-0, zk-1 і zk-2.

zk-0
zk-1
zk-2

Сервери в ансамблі ZooKeeper використовують натуральні числа як унікальні ідентифікатори, і зберігають ідентифікатор кожного сервера у файлі, який називається myid, в теці даних сервера.

Щоб переглянути вміст файлу myid для кожного сервера, скористайтеся такою командою.

for i in 0 1 2; do echo "myid zk-$i";kubectl exec zk-$i -- cat /var/lib/zookeeper/data/myid; done

Оскільки ідентифікатори є натуральними числами, а порядкові індекси є не відʼємними цілими числами, можна згенерувати ідентифікатор, додавши 1 до порядкового номера.

myid zk-0
1
myid zk-1
2
myid zk-2
3

Щоб отримати повне доменне імʼя (FQDN) кожного Podʼа у StatefulSet zk, використовуйте таку команду.

for i in 0 1 2; do kubectl exec zk-$i -- hostname -f; done

Service zk-hs створює домен для всіх Podʼів, zk-hs.default.svc.cluster.local.

zk-0.zk-hs.default.svc.cluster.local
zk-1.zk-hs.default.svc.cluster.local
zk-2.zk-hs.default.svc.cluster.local

Записи A в DNS Kubernetes перетворюють FQDN в IP-адреси Podʼів. Якщо Kubernetes переплановує Podʼи, він оновлює записи A із новими IP-адресами Podʼів, але імена записів A не змінюються.

ZooKeeper зберігає свою конфігурацію застосунку в файлі з іменем zoo.cfg. Використовуйте kubectl exec, щоб переглянути вміст файлу zoo.cfg у Поді zk-0.

kubectl exec zk-0 -- cat /opt/zookeeper/conf/zoo.cfg

У властивостях server.1, server.2 та server.3 внизу файлу, 1, 2 та 3 відповідають ідентифікаторам у файлах myid серверів ZooKeeper. Вони встановлені на FQDN для Podʼів у StatefulSet zk.

clientPort=2181
dataDir=/var/lib/zookeeper/data
dataLogDir=/var/lib/zookeeper/log
tickTime=2000
initLimit=10
syncLimit=2000
maxClientCnxns=60
minSessionTimeout= 4000
maxSessionTimeout= 40000
autopurge.snapRetainCount=3
autopurge.purgeInterval=0
server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888
server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888
server.3=zk-2.zk-hs.default.svc.cluster.local:2888:3888

Досягнення консенсусу

Протоколи консенсусу вимагають, щоб ідентифікатори кожного учасника були унікальними. Два учасники у протоколі Zab не повинні претендувати на той самий унікальний ідентифікатор. Це необхідно для того, щоб процеси в системі могли погодитися щодо того, які процеси затвердили які дані. Якщо запускаються два Podʼа з тим самим порядковим номером, два сервери ZooKeeper ідентифікуватимуть себе як один і той самий сервер.

kubectl get pods -w -l app=zk
NAME      READY     STATUS    RESTARTS   AGE
zk-0      0/1       Pending   0          0s
zk-0      0/1       Pending   0         0s
zk-0      0/1       ContainerCreating   0         0s
zk-0      0/1       Running   0         19s
zk-0      1/1       Running   0         40s
zk-1      0/1       Pending   0         0s
zk-1      0/1       Pending   0         0s
zk-1      0/1       ContainerCreating   0         0s
zk-1      0/1       Running   0         18s
zk-1      1/1       Running   0         40s
zk-2      0/1       Pending   0         0s
zk-2      0/1       Pending   0         0s
zk-2      0/1       ContainerCreating   0         0s
zk-2      0/1       Running   0         19s
zk-2      1/1       Running   0         40s

Записи A для кожного Pod вводяться, коли Pod стає готовим. Тому, FQDN серверів ZooKeeper посилається на єдину точку доступу, і ця точка доступу буде унікальним сервером ZooKeeper, який претендує на ідентифікацію, налаштовану в його файлі myid.

zk-0.zk-hs.default.svc.cluster.local
zk-1.zk-hs.default.svc.cluster.local
zk-2.zk-hs.default.svc.cluster.local

Це забезпечує, що властивості servers у файлах zoo.cfg ZooKeepers становлять собою правильно налаштований ансамбль.

server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888
server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888
server.3=zk-2.zk-hs.default.svc.cluster.local:2888:3888

Коли сервери використовують протокол Zab, щоб спробувати затвердити значення, вони або досягатимуть консенсусу і затверджуватимуть значення (якщо вибори лідера пройшли успішно і принаймні два Podʼа працюють та готові), або вони не зможуть цього зробити (якщо будь-яка з умов не виконується). Ні один стан не призведе до того, що один сервер підтверджує запис від імені іншого.

Перевірка адекватності ансамблю

Найбільш базове тестування на адекватність — це запис даних на один сервер ZooKeeper і читання даних з іншого.

Наведена нижче команда виконує скрипт zkCli.sh, щоб записати world в шлях /hello у Pod zk-0 в ансамблі.

kubectl exec zk-0 -- zkCli.sh create /hello world
WATCHER::

WatchedEvent state:SyncConnected type:None path:null
Created /hello

Щоб отримати дані з Podʼа zk-1, використовуйте таку команду.

kubectl exec zk-1 -- zkCli.sh get /hello

Дані, які ви створили на zk-0, доступні на всіх серверах в ансамблі.

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
world
cZxid = 0x100000002
ctime = Thu Dec 08 15:13:30 UTC 2016
mZxid = 0x100000002
mtime = Thu Dec 08 15:13:30 UTC 2016
pZxid = 0x100000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0

Забезпечення стійкості зберігання

Як зазначено в розділі Основи ZooKeeper, ZooKeeper фіксує всі записи в стійкому журналі (WAL) та періодично записує знімки стану памʼяті на носії. Використання WAL для забезпечення стійкості є поширеною технікою для застосунків, які використовують протоколи консенсусу для досягнення реплікованої машини станів.

Використовуйте команду kubectl delete, щоб видалити обʼєкт StatefulSet zk.

kubectl delete statefulset zk
statefulset.apps "zk" deleted

Спостерігайте за завершенням роботи Podʼів у StatefulSet.

kubectl get pods -w -l app=zk

Коли zk-0 повністю завершить роботу, використовуйте CTRL-C, щоб завершити виконання kubectl.

zk-2      1/1       Terminating   0         9m
zk-0      1/1       Terminating   0         11m
zk-1      1/1       Terminating   0         10m
zk-2      0/1       Terminating   0         9m
zk-2      0/1       Terminating   0         9m
zk-2      0/1       Terminating   0         9m
zk-1      0/1       Terminating   0         10m
zk-1      0/1       Terminating   0         10m
zk-1      0/1       Terminating   0         10m
zk-0      0/1       Terminating   0         11m
zk-0      0/1       Terminating   0         11m
zk-0      0/1       Terminating   0         11m

Повторно застосуйте маніфест у файлі zookeeper.yaml.

kubectl apply -f https://k8s.io/examples/application/zookeeper/zookeeper.yaml

Це створює обʼєкт StatefulSet zk, але інші обʼєкти API у маніфесті не модифікуються, оскільки вони вже існують.

Спостерігайте, як контролер StatefulSet перестворює Podʼи StatefulSet.

kubectl get pods -w -l app=zk

Коли Pod zk-2 відзначається як Running і Ready, використовуйте CTRL-C, щоб завершити виконання kubectl.

NAME      READY     STATUS    RESTARTS   AGE
zk-0      0/1       Pending   0          0s
zk-0      0/1       Pending   0         0s
zk-0      0/1       ContainerCreating   0         0s
zk-0      0/1       Running   0         19s
zk-0      1/1       Running   0         40s
zk-1      0/1       Pending   0         0s
zk-1      0/1       Pending   0         0s
zk-1      0/1       ContainerCreating   0         0s
zk-1      0/1       Running   0         18s
zk-1      1/1       Running   0         40s
zk-2      0/1       Pending   0         0s
zk-2      0/1       Pending   0         0s
zk-2      0/1       ContainerCreating   0         0s
zk-2      0/1       Running   0         19s
zk-2      1/1       Running   0         40s

Використайте наведену нижче команду, щоб отримати значення, яке ви ввели під час тестування на адекватність, з Podʼа zk-2.

kubectl exec zk-2 zkCli.sh get /hello

Навіть якщо ви зупинили та знову створили всі Podʼи в StatefulSet zk, ансамбль все ще обслуговує початкове значення.

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
world
cZxid = 0x100000002
ctime = Thu Dec 08 15:13:30 UTC 2016
mZxid = 0x100000002
mtime = Thu Dec 08 15:13:30 UTC 2016
pZxid = 0x100000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0

Секція volumeClaimTemplates поля spec обʼєкта zk StatefulSet вказує на PersistentVolume, який створюється для кожного Podʼа.

volumeClaimTemplates:
  - metadata:
      name: datadir
      annotations:
        volume.alpha.kubernetes.io/storage-class: anything
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 20Gi

Контролер StatefulSet генерує PersistentVolumeClaim для кожного Podʼа у StatefulSet.

Використайте наступну команду, щоб отримати PersistentVolumeClaims StatefulSet.

kubectl get pvc -l app=zk

Коли StatefulSet повторно створює Podʼи, він повторно монтує PersistentVolumes Podʼів.

NAME           STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
datadir-zk-0   Bound     pvc-bed742cd-bcb1-11e6-994f-42010a800002   20Gi       RWO           1h
datadir-zk-1   Bound     pvc-bedd27d2-bcb1-11e6-994f-42010a800002   20Gi       RWO           1h
datadir-zk-2   Bound     pvc-bee0817e-bcb1-11e6-994f-42010a800002   20Gi       RWO           1h

Розділ volumeMounts контейнера template StatefulSet монтує PersistentVolumes в теки даних серверів ZooKeeper.

volumeMounts:
- name: datadir
  mountPath: /var/lib/zookeeper

Коли Pod у zk StatefulSet (пере)планується, у нього завжди монтується той самий PersistentVolume у теку даних сервера ZooKeeper. Навіть коли Podʼи переплануються, всі записи, зроблені у логах WAL серверів ZooKeeper, та всі їх знімки залишаються стійкими.

Забезпечення однорідної конфігурації

Як вказано в розділах Забезпечення виборів лідера та Досягнення консенсусу, сервери в ансамблі ZooKeeper потребують однорідної конфігурації для вибору лідера та формування кворуму. Також потрібна однорідна конфігурація протоколу Zab для коректної роботи протоколу мережею. У нашому прикладі ми досягаємо однорідної конфігурації, вбудувавши конфігурацію безпосередньо у маніфест.

Отримайте zk StatefulSet.

kubectl get sts zk -o yaml

command:
      - sh
      - -c
      - "start-zookeeper \
        --servers=3 \
        --data_dir=/var/lib/zookeeper/data \
        --data_log_dir=/var/lib/zookeeper/data/log \
        --conf_dir=/opt/zookeeper/conf \
        --client_port=2181 \
        --election_port=3888 \
        --server_port=2888 \
        --tick_time=2000 \
        --init_limit=10 \
        --sync_limit=5 \
        --heap=512M \
        --max_client_cnxns=60 \
        --snap_retain_count=3 \
        --purge_interval=12 \
        --max_session_timeout=40000 \
        --min_session_timeout=4000 \
        --log_level=INFO"

Команда, яка використовується для запуску серверів ZooKeeper, передає конфігурацію як параметр командного рядка. Ви також можете використовувати змінні середовища для передачі конфігурації в ансамбль.

Налаштування системи логування

Один з файлів, що генерується сценарієм zkGenConfig.sh, керує логуванням ZooKeeper. ZooKeeper використовує Log4j, і типово він використовує інструмент постійного додавання, який покладається на час та розмір для конфігурації логування.

Використайте команду нижче, щоб отримати конфігурацію логування з одного з контейнерів у StatefulSet zk.

kubectl exec zk-0 cat /usr/etc/zookeeper/log4j.properties

Конфігурація логування нижче призведе до того, що процес ZooKeeper буде записувати всі свої логи у вихідний файловий потік стандартного виводу.

zookeeper.root.logger=CONSOLE
zookeeper.console.threshold=INFO
log4j.rootLogger=${zookeeper.root.logger}
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=${zookeeper.console.threshold}
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n

Це найпростіший спосіб безпечного логування всередині контейнера. Тому що програми записують логи у стандартний вивід, Kubernetes буде відповідальним за ротацію логів для вас. Kubernetes також реалізує розумну політику зберігання, яка гарантує, що логи застосунків, записані у потоки стандартного виводу та помилок, не виснажують локальні носії інформації.

Використайте kubectl logs, щоб отримати останні 20 рядків логів з одного з контейнерів.

kubectl logs zk-0 --tail 20

Ви можете переглядати логи застосунків, записані у стандартний вивід або стандартну помилку, використовуючи kubectl logs та з Kubernetes Dashboard.

2016-12-06 19:34:16,236 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52740
2016-12-06 19:34:16,237 [myid:1] - INFO  [Thread-1136:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52740 (no session established for client)
2016-12-06 19:34:26,155 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52749
2016-12-06 19:34:26,155 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52749
2016-12-06 19:34:26,156 [myid:1] - INFO  [Thread-1137:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52749 (no session established for client)
2016-12-06 19:34:26,222 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52750
2016-12-06 19:34:26,222 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52750
2016-12-06 19:34:26,226 [myid:1] - INFO  [Thread-1138:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52750 (no session established for client)
2016-12-06 19:34:36,151 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52760
2016-12-06 19:34:36,152 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52760
2016-12-06 19:34:36,152 [myid:1] - INFO  [Thread-1139:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52760 (no session established for client)
2016-12-06 19:34:36,230 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52761
2016-12-06 19:34:36,231 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52761
2016-12-06 19:34:36,231 [myid:1] - INFO  [Thread-1140:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52761 (no session established for client)
2016-12-06 19:34:46,149 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52767
2016-12-06 19:34:46,149 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52767
2016-12-06 19:34:46,149 [myid:1] - INFO  [Thread-1141:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52767 (no session established for client)
2016-12-06 19:34:46,230 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52768
2016-12-06 19:34:46,230 [myid:1] - INFO  [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52768
2016-12-06 19:34:46,230 [myid:1] - INFO  [Thread-1142:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52768 (no session established for client)

Kubernetes інтегрується з багатьма рішеннями для логування. Ви можете вибрати рішення для логування, яке найкраще підходить для вашого кластера та застосунків. Для логування та агрегації на рівні кластера розгляньте розгортання sidecar контейнера, для ротації та надсилання ваших логів.

Налаштування не привілейованого користувача

Найкращі практики дозволити застосунку працювати як привілейований користувач всередині контейнера — це предмет для обговорення. Якщо ваша організація вимагає, щоб застосунки працювали як непривілейований користувач, ви можете використовувати SecurityContext для контролю над користувачем, під яким запускається точка входу.

У template Podʼів StatefulSet zk є SecurityContext.

securityContext:
  runAsUser: 1000
  fsGroup: 1000

У контейнерах Podʼів, UID 1000 відповідає користувачеві zookeeper, а GID 1000 відповідає групі zookeeper.

Отримайте інформацію про процес ZooKeeper з Поду zk-0.

kubectl exec zk-0 -- ps -elf

Оскільки поле runAsUser обʼєкта securityContext встановлене на 1000, замість того, щоб запускатися як root, процес ZooKeeper запускається як користувач zookeeper.

F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
4 S zookeep+     1     0  0  80   0 -  1127 -      20:46 ?        00:00:00 sh -c zkGenConfig.sh && zkServer.sh start-foreground
0 S zookeep+    27     1  0  80   0 - 1155556 -    20:46 ?        00:00:19 /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dzookeeper.log.dir=/var/log/zookeeper -Dzookeeper.root.logger=INFO,CONSOLE -cp /usr/bin/../build/classes:/usr/bin/../build/lib/*.jar:/usr/bin/../share/zookeeper/zookeeper-3.4.9.jar:/usr/bin/../share/zookeeper/slf4j-log4j12-1.6.1.jar:/usr/bin/../share/zookeeper/slf4j-api-1.6.1.jar:/usr/bin/../share/zookeeper/netty-3.10.5.Final.jar:/usr/bin/../share/zookeeper/log4j-1.2.16.jar:/usr/bin/../share/zookeeper/jline-0.9.94.jar:/usr/bin/../src/java/lib/*.jar:/usr/bin/../etc/zookeeper: -Xmx2G -Xms2G -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /usr/bin/../etc/zookeeper/zoo.cfg

Типово, коли PersistentVolumes Поду монтується в теку даних сервера ZooKeeper, він доступний тільки для користувача root. Ця конфігурація заважає процесу ZooKeeper записувати свій лог та зберігати свої знімки.

Використовуйте команду нижче, щоб отримати права доступу до файлу теки даних ZooKeeper на Поді zk-0.

kubectl exec -ti zk-0 -- ls -ld /var/lib/zookeeper/data

Оскільки поле fsGroup обʼєкта securityContext встановлене на 1000, власність PersistentVolumes Podʼів встановлюється на групу zookeeper, і процес ZooKeeper може читати та записувати свої дані.

drwxr-sr-x 3 zookeeper zookeeper 4096 Dec  5 20:45 /var/lib/zookeeper/data

Управління процесом ZooKeeper

В документації ZooKeeper зазначено, що "Вам захочеться мати наглядовий процес, який керує кожним з ваших процесів сервера ZooKeeper (JVM)." Використання watchdog (наглядового процесу) для перезапуску процесів, що зазнали збою, у розподіленій системі є загальним шаблоном. При розгортанні застосунку в Kubernetes, замість використання зовнішньої утиліти як наглядового процесу, ви повинні використовувати Kubernetes як watchdog для вашого застосунку.

Оновлення ансамблю

StatefulSet zk налаштований на використання стратегії оновлення RollingUpdate.

Ви можете використовувати kubectl patch, щоб оновити кількість cpus, виділених серверам.

kubectl patch sts zk --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/cpu", "value":"0.3"}]'
statefulset.apps/zk відредаговано

Використовуйте kubectl rollout status, щоб спостерігати за статусом оновлення.

kubectl rollout status sts/zk
waiting for statefulset rolling update to complete 0 pods at revision zk-5db4499664...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
waiting for statefulset rolling update to complete 1 pods at revision zk-5db4499664...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
waiting for statefulset rolling update to complete 2 pods at revision zk-5db4499664...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
statefulset rolling update complete 3 pods at revision zk-5db4499664...

Це припиняє роботу Podʼів, по одному, у зворотному порядку, і перестворює їх з новою конфігурацією. Це забезпечує підтримку кворуму під час постійного оновлення.

Використовуйте команду kubectl rollout history, щоб переглянути історію або попередні конфігурації.

kubectl rollout history sts/zk

Вивід схожий на такий:

statefulsets "zk"
REVISION
1
2

Використовуйте команду kubectl rollout undo, щоб скасувати модифікацію.

kubectl rollout undo sts/zk

Вивід схожий на такий:

statefulset.apps/zk повернувся до попереднього стану

Обробка відмови процесу

Політики перезапуску контролюють, як Kubernetes обробляє відмови процесів для вхідної точки контейнера в Pod. Для Podʼів у StatefulSet єдине припустиме значення RestartPolicy — це Always, і це є стандартним значенням. Для StatefulSet застосунків ви ніколи не повинні змінювати стандартні значення.

Використовуйте наступну команду, щоб переглянути дерево процесів сервера ZooKeeper, який працює в поді zk-0.

kubectl exec zk-0 -- ps -ef

Команда, яка використовується як точка входу контейнера, має PID 1, а процес ZooKeeper, нащадок точки входу, має PID 27.

UID        PID  PPID  C STIME TTY          TIME CMD
zookeep+     1     0  0 15:03 ?        00:00:00 sh -c zkGenConfig.sh && zkServer.sh start-foreground
zookeep+    27     1  0 15:03 ?        00:00:03 /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dzookeeper.log.dir=/var/log/zookeeper -Dzookeeper.root.logger=INFO,CONSOLE -cp /usr/bin/../build/classes:/usr/bin/../build/lib/*.jar:/usr/bin/../share/zookeeper/zookeeper-3.4.9.jar:/usr/bin/../share/zookeeper/slf4j-log4j12-1.6.1.jar:/usr/bin/../share/zookeeper/slf4j-api-1.6.1.jar:/usr/bin/../share/zookeeper/netty-3.10.5.Final.jar:/usr/bin/../share/zookeeper/log4j-1.2.16.jar:/usr/bin/../share/zookeeper/jline-0.9.94.jar:/usr/bin/../src/java/lib/*.jar:/usr/bin/../etc/zookeeper: -Xmx2G -Xms2G -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /usr/bin/../etc/zookeeper/zoo.cfg

У іншому терміналі спостерігайте за Podʼами у StatefulSet zk за допомогою наступної команди.

kubectl get pod -w -l app=zk

У іншому терміналі завершіть процес ZooKeeper у поді zk-0 за допомогою наступної команди.

kubectl exec zk-0 -- pkill java

Завершення процесу ZooKeeper призвело до завершення його батьківського процесу. Оскільки RestartPolicy контейнера — завжди, він перезапустив батьківський процес.

NAME      READY     STATUS    RESTARTS   AGE
zk-0      1/1       Running   0          21m
zk-1      1/1       Running   0          20m
zk-2      1/1       Running   0          19m
NAME      READY     STATUS    RESTARTS   AGE
zk-0      0/1       Error     0          29m
zk-0      0/1       Running   1         29m
zk-0      1/1       Running   1         29m

Якщо ваш застосунок використовує сценарій (наприклад, zkServer.sh) для запуску процесу, який реалізує бізнес-логіку застосунку, сценарій повинен завершуватися разом з дочірнім процесом. Це забезпечує перезапуск контейнера застосунку, коли процес, що реалізує бізнес-логіку застосунку, зазнає збою.

Тестування на доступність

Налаштування вашого застосунку на перезапуск збійних процесів це ще не все, щоб забезпечити справність розподіленої системи. Існують сценарії, коли процеси системи можуть бути живими, але недоступними або інакше несправними. Вам слід використовувати проби на доступність, щоб повідомити Kubernetes про те, що процеси вашого застосунку є несправними, і його слід перезапустити.

Шаблон Pod для StatefulSet zk визначає пробу на доступність.

  livenessProbe:
    exec:
      command:
      - sh
      - -c
      - "zookeeper-ready 2181"
    initialDelaySeconds: 15
    timeoutSeconds: 5

Проба викликає скрипт bash, який використовує чотирилітерне слово ruok ZooKeeper для перевірки стану справності сервера.

OK=$(echo ruok | nc 127.0.0.1 $1)
if [ "$OK" == "imok" ]; then
    exit 0
else
    exit 1
fi

У одному вікні термінала використовуйте наступну команду, щоб спостерігати за подами у StatefulSet zk.

kubectl get pod -w -l app=zk

У іншому вікні, використовуйте наступну команду, щоб видалити скрипт zookeeper-ready з файлової системи Podʼа zk-0.

kubectl exec zk-0 -- rm /opt/zookeeper/bin/zookeeper-ready

Коли проба на доступність для процесу ZooKeeper невдала, Kubernetes автоматично перезапустить процес за вас, забезпечуючи тим самим перезапуск несправних процесів у наборі.

kubectl get pod -w -l app=zk
NAME      READY     STATUS    RESTARTS   AGE
zk-0      1/1       Running   0          1h
zk-1      1/1       Running   0          1h
zk-2      1/1       Running   0          1h
NAME      READY     STATUS    RESTARTS   AGE
zk-0      0/1       Running   0          1h
zk-0      0/1       Running   1         1h
zk-0      1/1       Running   1         1h

Тестування готовності

Готовність не те саме, що і доступність. Якщо процес живий, він запланований і справний. Якщо процес готовий, він може обробляти вхідні дані. Доступність є необхідною, але не достатньою, умовою для готовності. Є випадки, особливо під час ініціалізації та завершення, коли процес може бути доступним, але не готовим.

Якщо ви вказуєте пробу на готовність, Kubernetes буде переконуватися, що процеси вашого застосунку не отримуватимуть мережевого трафіку до тих пір, поки їхні перевірки готовності не пройдуть.

Для сервера ZooKeeper, доступність означає готовність. Тому проба готовності з маніфесту zookeeper.yaml ідентична пробі на доступність.

  readinessProbe:
    exec:
      command:
      - sh
      - -c
      - "zookeeper-ready 2181"
    initialDelaySeconds: 15
    timeoutSeconds: 5

Навіть якщо проби на доступність та готовність ідентичні, важливо вказати обидві. Це забезпечує, що мережевий трафік отримуватимуть лише справні сервери в ансамблі ZooKeeper.

Толерантність до відмов вузлів

Для успішного збереження змін даних ZooKeeper потрібен кворум серверів. Для ансамблю з трьох серверів, два сервери повинні бути справними, щоб записи вдалися. У системах на основі кворуму учасники розгорнені в областях відмов для забезпечення доступності. Щоб уникнути перебоїв через втрату окремої машини, найкращі практики виключають спільне розташування кількох екземплярів застосунку на одній машині.

Станадартно Kubernetes може розміщувати Podʼи в StatefulSet на тому ж вузлі. Для створеного вами ансамблю з трьох серверів, якщо два сервери знаходяться на тому ж вузлі, і цей вузол виходить з ладу, клієнти вашої служби ZooKeeper зазнають перебою, поки хоча б один з Podʼів не буде перепланований.

Ви завжди повинні надавати додаткові можливості для перепланування процесів критичних систем у разі відмови вузла. Якщо ви це зробите, перерва буде тривати лише до тих пір, поки планувальник Kubernetes перепланує один з серверів ZooKeeper. Однак, якщо ви хочете, щоб ваша служба терпіла відмови вузлів без перебою роботи, вам слід встановити podAntiAffinity.

Використовуйте наведену нижче команду, щоб отримати вузли для Podʼів у StatefulSet zk.

for i in 0 1 2; do kubectl get pod zk-$i --template={{.spec.nodeName}}; echo ""; done

Всі Podʼи у StatefulSet zk розгорнуті на різних вузлах.

kubernetes-node-cxpk
kubernetes-node-a5aq
kubernetes-node-2g2d

Це через те, що у Podʼах у StatefulSet zk вказано PodAntiAffinity.

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
            - key: "app"
              operator: In
              values:
                - zk
        topologyKey: "kubernetes.io/hostname"

Поле requiredDuringSchedulingIgnoredDuringExecution говорить планувальнику Kubernetes, що він ніколи не повинен розміщувати два Podʼи з міткою app як zk у домені, визначеному за допомогою topologyKey. Ключ topologyKey kubernetes.io/hostname вказує, що домен — це окремий вузол. Використовуючи різні правила, мітки та селектори, ви можете розширити цей метод, щоб розподілити свій ансамбль на фізичні, мережеві та домени відмов.

Виживання під час обслуговування

У цьому розділі ви здійсните блокування та виведення вузлів для обслуговування. Якщо ви використовуєте цей посібник на спільному кластері, переконайтеся, що це не позначиться негативно на інших мешканцях.

Попередній розділ показав, як розподілити ваші Podʼи по вузлах, щоб вижити в разі непередбачуваних відмов вузлів, але вам також потрібно планувати тимчасові відмови вузлів, які виникають через заплановане обслуговування.

Використайте цю команду, щоб отримати вузли у вашому кластері.

kubectl get nodes

У цьому посібнику передбачається наявність кластера з щонайменше чотирма вузлами. Якщо в кластері є більше чотирьох вузлів, використовуйте kubectl cordon, щоб заборонити доступ до всіх вузлів, окрім чотирьох. Обмеження до чотирьох вузлів гарантуватиме, що Kubernetes врахує обмеження подібності та PodDisruptionBudget при плануванні Podʼів zookeeper у наступній симуляції обслуговування.

kubectl cordon <імʼя-вузла>

Використайте цю команду, щоб отримати zk-pdb PodDisruptionBudget.

kubectl get pdb zk-pdb

Поле max-unavailable показує Kubernetes, що в будь-який момент може бути недоступний найбільше один Pod з StatefulSet zk.

NAME      MIN-AVAILABLE   MAX-UNAVAILABLE   ALLOWED-DISRUPTIONS   AGE
zk-pdb    N/A             1                 1

У одному терміналі використовуйте цю команду, щоб переглядати Podʼи у StatefulSet zk.

kubectl get pods -w -l app=zk

У іншому терміналі використовуйте цю команду, щоб отримати вузли, на яких наразі заплановані Podʼи.

for i in 0 1 2; do kubectl get pod zk-$i --template {{.spec.nodeName}}; echo ""; done

Вихідна інформація подібна до наступної:

kubernetes-node-pb41
kubernetes-node-ixsl
kubernetes-node-i4c4

Використовуйте kubectl drain, щоб заблокувати та вивести з використання вузол, на якому запланований Pod zk-0.

kubectl drain $(kubectl get pod zk-0 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data

Вихідна інформація подібна до наступної:

node "kubernetes-node-pb41" cordoned

WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-pb41, kube-proxy-kubernetes-node-pb41; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-o5elz
pod "zk-0" deleted
node "kubernetes-node-pb41" drained

Оскільки у вашому кластері є чотири вузла, команда kubectl drain успішно виконується, і zk-0 перепланований на інший вузол.

NAME      READY     STATUS    RESTARTS   AGE
zk-0      1/1       Running   2          1h
zk-1      1/1       Running   0          1h
zk-2      1/1       Running   0          1h
NAME      READY     STATUS        RESTARTS   AGE
zk-0      1/1       Terminating   2          2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Pending   0         0s
zk-0      0/1       Pending   0         0s
zk-0      0/1       ContainerCreating   0         0s
zk-0      0/1       Running   0         51s
zk-0      1/1       Running   0         1m

Продовжуйте слідкувати за Podʼами StatefulSet у першому терміналі і виведіть вузол, на якому запланований zk-1.

kubectl drain $(kubectl get pod zk-1 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data

Вихідна інформація подібна до наступної:

"kubernetes-node-ixsl" cordoned
WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-ixsl, kube-proxy-kubernetes-node-ixsl; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-voc74
pod "zk-1" deleted
node "kubernetes-node-ixsl" drained

Pod zk-1 не може бути перепланований, оскільки StatefulSet zk містить правило PodAntiAffinity, яке запобігає спільному розташуванню Podʼів, і так як доступні тільки два вузла, Pod залишиться в стані очікування.

kubectl get pods -w -l app=zk

Вихідна інформація подібна до наступної:

NAME      READY     STATUS    RESTARTS   AGE
zk-0      1/1       Running   2          1h
zk-1      1/1       Running   0          1h
zk-2      1/1       Running   0          1h
NAME      READY     STATUS        RESTARTS   AGE
zk-0      1/1       Terminating   2          2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Pending   0         0s
zk-0      0/1       Pending   0         0s
zk-0      0/1       ContainerCreating   0         0s
zk-0      0/1       Running   0         51s
zk-0      1/1       Running   0         1m
zk-1      1/1       Terminating   0         2h
zk-1      0/1       Terminating   0         2h
zk-1      0/1       Terminating   0         2h
zk-1      0/1       Terminating   0         2h
zk-1      0/1       Pending   0         0s
zk-1      0/1       Pending   0         0s

Продовжуйте слідкувати за Podʼами StatefulSet, і виведіть з використання вузол, на якому запланований zk-2.

kubectl drain $(kubectl get pod zk-2 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data

Вихідна інформація подібна до наступної:

node "kubernetes-node-i4c4" cordoned

WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog
WARNING: Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog; Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4
There are pending pods when an error occurred: Cannot evict pod as it would violate the pod's disruption budget.
pod/zk-2

Використовуйте CTRL-C для припинення роботи kubectl.

Ви не можете вивести з роботи третій вузол, оскільки zk-2 буде порушувати zk-budget. Однак, вузол залишатиметься заблокованим (cordoned).

Використовуйте zkCli.sh для отримання значень введених впродовж перевірки адекватності zk-0.

kubectl exec zk-0 zkCli.sh get /hello

Service все ще доступний, оскільки його PodDisruptionBudget не порушено.

WatchedEvent state:SyncConnected type:None path:null
world
cZxid = 0x200000002
ctime = Wed Dec 07 00:08:59 UTC 2016
mZxid = 0x200000002
mtime = Wed Dec 07 00:08:59 UTC 2016
pZxid = 0x200000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0

Використовуйте kubectl uncordon, щоб розблокувати вузол.

kubectl uncordon kubernetes-node-pb41

Вивід буде подібний до наступного:

node "kubernetes-node-pb41" uncordoned

zk-1 переплановано на цей вузол. Зачекайте доки zk-1 не буде Running та Ready.

kubectl get pods -w -l app=zk

Вивід буде подібний до наступного:

NAME      READY     STATUS    RESTARTS   AGE
zk-0      1/1       Running   2          1h
zk-1      1/1       Running   0          1h
zk-2      1/1       Running   0          1h
NAME      READY     STATUS        RESTARTS   AGE
zk-0      1/1       Terminating   2          2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Terminating   2         2h
zk-0      0/1       Pending   0         0s
zk-0      0/1       Pending   0         0s
zk-0      0/1       ContainerCreating   0         0s
zk-0      0/1       Running   0         51s
zk-0      1/1       Running   0         1m
zk-1      1/1       Terminating   0         2h
zk-1      0/1       Terminating   0         2h
zk-1      0/1       Terminating   0         2h
zk-1      0/1       Terminating   0         2h
zk-1      0/1       Pending   0         0s
zk-1      0/1       Pending   0         0s
zk-1      0/1       Pending   0         12m
zk-1      0/1       ContainerCreating   0         12m
zk-1      0/1       Running   0         13m
zk-1      1/1       Running   0         13m

Спробуйте вивести вузол з обслуговування на якмоу запланований zk-2.

kubectl drain $(kubectl get pod zk-2 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data

Вивід буде подібний до наступного:

node "kubernetes-node-i4c4" already cordoned
WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog
pod "heapster-v1.2.0-2604621511-wht1r" deleted
pod "zk-2" deleted
node "kubernetes-node-i4c4" drained

На цей раз kubectl drain успішно виконується.

Розблокуйте другий вузол, щоб дозволити перепланування zk-2.

kubectl uncordon kubernetes-node-ixsl

Вивід буде подібний до наступного:

node "kubernetes-node-ixsl" uncordoned

Ви можете використовувати kubectl drain разом з PodDisruptionBudgets, щоб забезпечити доступність ваших служб під час обслуговування. Якщо drain використовується для блокування вузлів та видалення Podʼів до виключення вузла з експлуатації для обслуговування, служби, які виражають бюджет відмов, мають поважати цей бюджет . Ви завжди повинні виділяти додаткову потужність для критичних служб, щоб їх Podʼи могли негайно бути переплановані.

Очищення

  • Використайте kubectl uncordon, щоб розблокувати всі вузли у вашому кластері.
  • Ви повинні видалити носії постійного носія для PersistentVolumes, використаних у цьому посібнику. Дотримуйтеся необхідних кроків, залежно від вашого середовища, конфігурації зберігання та методу надання послуг, щоб переконатися, що всі збережені дані вилучено.

7 - Керування кластером

7.1 - Запуск автономного Kublet

Цей посібник показує, як запустити автономний екземпляр kubelet.

У вас можуть бути різні причини для запуску автономного kubelet. Цей посібник спрямований на ознайомлення вас з Kubernetes, навіть якщо у вас немає багато досвіду роботи з ним. Ви можете слідувати цьому посібнику і дізнатися про налаштування вузла, основних (статичних) Podʼів та як Kubernetes керує контейнерами.

Після того, як ви пройдете цей посібник, ви можете спробувати використовувати кластер, який має панель управління для керування podʼами та вузлами, а також іншими типами обʼєктів. Ознайомтесь з прикладом в розділі Привіт, minikube.

Ви також можете запустити kubelet в автономному режимі для задоволення операційних потреб, таких як запуск панелі управління для високо доступного, стійкого до збоїв кластера. Цей посібник не охоплює деталі, необхідні для запуску високодоступної панелі управління.

Цілі

  • Встановлення cri-o та kubelet в системах Linux та їх запуск як служб systemd.
  • Запуск Podʼа для nginx що очікує запитів на порту TCP 80 за IP адресою Podʼа.
  • Дізнатись, як різні компоненти взаємодіють один з одним.

Перш ніж ви розпочнете

  • Адміністраторський (root) доступ до системи Linux, яка використовує systemd та iptables (або nftables з емуляцією iptables).
  • Доступ до Інтернету для завантаження компонентів, необхідних для роботи з підручником, зокрема

Підготовка системи

Конфігурація підкачки

Стандартно kubelet не запускається, якщо на вузлі виявлено використання файлу підкачки (swap). Це означає, що підкачку необхідно або вимкнути, або налаштувати толерантність до неї у kubelet.

Якщо у вас увімкнена підкачка, вимкніть її або додайте параметр failSwapOn: false у файл конфігурації kubelet.

Щоб перевірити, чи увімкнена підкачка:

sudo swapon --show

Якщо команда не виводить жодної інформації, то підкачка вже вимкнена.

Щоб тимчасово вимкнути підкачку:

sudo swapoff -a

Щоб зробити це налаштування постійним після перезавантаження:

Переконайтеся, що підкачка вимкнена в файлі /etc/fstab або у файлі systemd.swap, залежно від того, як вона була налаштована на вашій системі.

Увімкнення пересилання пакетів IPv4

Щоб перевірити, чи увімкнено пересилання пакетів IPv4:

cat /proc/sys/net/ipv4/ip_forward

Якщо результатом є 1, пересилання вже увімкнено. Якщо результатом є 0, виконайте наступні кроки.

Щоб увімкнути пересилання пакетів IPv4, створіть конфігураційний файл, який встановлює параметр net.ipv4.ip_forward у значення 1:

sudo tee /etc/sysctl.d/k8s.conf <<EOF
net.ipv4.ip_forward = 1
EOF

Застосуйте зміни до системи:

sudo sysctl --system

Результат буде подібним до цього:

...
* Applying /etc/sysctl.d/k8s.conf ...
net.ipv4.ip_forward = 1
* Applying /etc/sysctl.conf ...

Завантаження, встановлення та налаштування компонентів

Встановлення середовища виконання контейнерів

Завантажте найновіші доступні версії необхідних пакетів (рекомендовано).

Цей підручник пропонує встановити середовище виконання контейнерів CRI-O (зовнішнє посилання).

Існує кілька способів встановлення середовища виконання CRI-O, залежно від вашого дистрибутиву Linux. Хоча CRI-O рекомендує використовувати пакети deb або rpm, у цьому підручнику використовується скрипт статичного бінарного пакета проєкту CRI-O Packaging, щоб спростити процес і зробити його незалежним від дистрибутиву.

Скрипт встановлює та налаштовує додаткове необхідне програмне забезпечення, таке як cni-plugins для контейнерної мережі та crun і runc для запуску контейнерів.

Скрипт автоматично визначить архітектуру процесора вашої системи (amd64 або arm64), а також обере та встановить найновіші версії програмних пакетів.

Налаштування CRI-O

Відвідайте сторінку випусків (зовнішнє посилання).

Завантажте скрипт статичного бінарного пакета:

curl https://raw.githubusercontent.com/cri-o/packaging/main/get > crio-install

Запустіть інсталяційний скрипт:

sudo bash crio-install

Увімкніть і запустіть службу crio:

sudo systemctl daemon-reload
sudo systemctl enable --now crio.service

Швидка перевірка:

sudo systemctl is-active crio.service

Результат подібний до:

active

Детальна перевірка служби:

sudo journalctl -f -u crio.service

Встановлення мережевих втулків

Інсталятор cri-o встановлює та налаштовує пакет cni-plugins. Ви можете перевірити встановлення, виконавши таку команду:

/opt/cni/bin/bridge --version

Результат буде подібний до:

CNI bridge plugin v1.5.1
CNI protocol versions supported: 0.1.0, 0.2.0, 0.3.0, 0.3.1, 0.4.0, 1.0.0

Щоб перевірити стандартну конфігурацію:

cat /etc/cni/net.d/11-crio-ipv4-bridge.conflist

Результат буде подібний до:

{
  "cniVersion": "1.0.0",
  "name": "crio",
  "plugins": [
    {
      "type": "bridge",
      "bridge": "cni0",
      "isGateway": true,
      "ipMasq": true,
      "hairpinMode": true,
      "ipam": {
        "type": "host-local",
        "routes": [
            { "dst": "0.0.0.0/0" }
        ],
        "ranges": [
            [{ "subnet": "10.85.0.0/16" }]
        ]
      }
    }
  ]
}

Завантаження та встановлення

Завантажте останній стабільний випуск kubelet.


curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubelet"


curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/arm64/kubelet"

Налаштування:

sudo mkdir -p /etc/kubernetes/manifests
sudo tee /etc/kubernetes/kubelet.yaml <<EOF
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
authentication:
  webhook:
    enabled: false # НЕ використовуйте у промислових кластерах!
authorization:
  mode: AlwaysAllow # НЕ використовуйте у промислових кластерах!
enableServer: false
logging:
  format: text
address: 127.0.0.1 # Restrict access to localhost
readOnlyPort: 10255 # НЕ використовуйте у промислових кластерах!
staticPodPath: /etc/kubernetes/manifests
containerRuntimeEndpoint: unix:///var/run/crio/crio.sock
EOF

Встановлення:

chmod +x kubelet
sudo cp kubelet /usr/bin/

Створіть файл служби systemd:

sudo tee /etc/systemd/system/kubelet.service <<EOF
[Unit]
Description=Kubelet

[Service]
ExecStart=/usr/bin/kubelet \
 --config=/etc/kubernetes/kubelet.yaml
Restart=always

[Install]
WantedBy=multi-user.target
EOF

Аргумент командного рядка --kubeconfig було навмисно пропущено у файлі конфігурації конфігураційного файлу служби. Цей аргумент задає шлях до файлу kubeconfig файл, який визначає, як підключатися до сервера API, вмикаючи режим сервера API. Якщо його не вказати, увімкнеться автономний режим.

Увімкніть та запустіть службу kubelet:

sudo systemctl daemon-reload
sudo systemctl enable --now kubelet.service

Швидкий тест:

sudo systemctl is-active kubelet.service

Вивід має бути подібним до:

active

Докладна перевірка служби:

sudo journalctl -u kubelet.service

Перевірте точку доступу API kubelet /healthz:

curl http://localhost:10255/healthz?verbose

Вивід має бути подібним до:

[+]ping ok
[+]log ok
[+]syncloop ok
healthz check passed

Запит до точки доступу до API kubelet /pods:

curl http://localhost:10255/pods | jq '.'

Вивід має бути подібним до:

{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {},
  "items": null
}

Запуск Podʼів в kubelet

В автономному режимі ви можете запускати Podʼи за допомогою маніфестів Pod'ів. Маніфести можуть або знаходитися у локальній файловій системі, або бути отриманими по HTTP з джерела конфігурації.

Створіть маніфест для Pod:

cat <<EOF > static-web.yaml
apiVersion: v1
kind: Pod
metadata:
  name: static-web
spec:
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
          protocol: TCP
EOF

Скопіюйте файл маніфесту static-web.yaml до теки /etc/kubernetes/manifests.

sudo cp static-web.yaml /etc/kubernetes/manifests/

Дізнатись відомості про kubelet та Pod

Мережевий втулок Podʼа створює мережевий міст (cni0) і пару інтерфейсів veth для кожного Podʼа (один з пари знаходиться всередині новоствореного Podʼа, а інший — на рівні хосту).

Зверніться до точки доступу до API kubelet за адресою http://localhost:10255/pods:

curl http://localhost:10255/pods | jq '.'

Отримання IP-адреси static-web Podʼа:

curl http://localhost:10255/pods | jq '.items[].status.podIP'

Вивід має бути подібним до:

"10.85.0.4"

Приєднайтеся до сервера nginx за адресою http://<IP>:<Порт> (стандартний порт 80), в цьому випадку:

curl http://10.85.0.4

Вивід має бути подібним до:

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Де шукати більш детальну інформацію

Якщо вам потрібно діагностувати проблему, пов'язану з роботою цього посібника, ви можете зазирнути в наступні теки для моніторингу та усунення несправностей:

/var/lib/cni
/var/lib/containers
/var/lib/kubelet

/var/log/containers
/var/log/pods

Очищення

kubelet

sudo systemctl disable --now kubelet.service
sudo systemctl daemon-reload
sudo rm /etc/systemd/system/kubelet.service
sudo rm /usr/bin/kubelet
sudo rm -rf /etc/kubernetes
sudo rm -rf /var/lib/kubelet
sudo rm -rf /var/log/containers
sudo rm -rf /var/log/pods

Середовище виконання контейнерів

sudo systemctl disable --now crio.service
sudo systemctl daemon-reload
sudo rm -rf /usr/local/bin
sudo rm -rf /usr/local/lib
sudo rm -rf /usr/local/share
sudo rm -rf /usr/libexec/crio
sudo rm -rf /etc/crio
sudo rm -rf /etc/containers

Мережеві втулки

sudo rm -rf /opt/cni
sudo rm -rf /etc/cni
sudo rm -rf /var/lib/cni

Висновок

На цій сторінці було розглянуто основні аспекти розгортання kubelet в автономному режимі. Тепер ви готові розгортати Podʼи та тестувати додаткову функціональність.

Зверніть увагу, що в автономному режимі kubelet не підтримує отримання конфігурацій Podʼів із панелі управління (оскільки немає підключення до панелі управління).

Також ви не можете використовувати ConfigMap або Secret для налаштування контейнерів у статичному Pod.

Що далі

8 - Services

8.1 - Підключення застосунків за допомогою Service

Модель Kubernetes для підключення контейнерів

Тепер, коли у вас є постійно працюючий, реплікований застосунок, ви можете дати до нього доступ з мережі.

Kubernetes передбачає, що кожен з Podʼів може спілкуватися з іншими, незалежно від того, на якому хості вони опиняються. Kubernetes надає кожному Podʼу власну IP-адресу, що є приватною адресою кластера, тому вам не потрібно явно створювати посилання між Podʼами або перенаправляти порти контейнера на порти хосту. Це означає, що контейнери всередині Podʼів можуть звертатися до портів один одного на localhost, і всі Podʼи в кластері можуть бачити один одного без NAT. У решті цього документа розглянуто, як ви можете запустити надійні сервіси на такій мережевій моделі.

У цьому посібнику використовується простий вебсервер nginx для демонстрації концепції.

Експонування Podʼів в кластер

Ми робили це в попередньому прикладі, але зробимо це ще раз і зосередимося на перспективі мережі. Створіть Pod nginx, і зверніть увагу, що він має специфікацію порту контейнера:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80

Це робить його доступним з будь-якого вузла у вашому кластері. Перевірте вузли, на яких працює Pod:

kubectl apply -f ./run-my-nginx.yaml
kubectl get pods -l run=my-nginx -o wide
NAME                        READY     STATUS    RESTARTS   AGE       IP            NODE
my-nginx-3800858182-jr4a2   1/1       Running   0          13s       10.244.3.4    kubernetes-minion-905m
my-nginx-3800858182-kna2y   1/1       Running   0          13s       10.244.2.5    kubernetes-minion-ljyd

Перевірте IP-адреси ваших Podʼів:

kubectl get pods -l run=my-nginx -o custom-columns=POD_IP:.status.podIPs
    POD_IP
    [map[ip:10.244.3.4]]
    [map[ip:10.244.2.5]]

Ви повинні мати можливість увійти у будь-який вузол у своєму кластері за допомогою SSH і використовувати інструмент, такий як curl, щоб робити запити до обох IP-адрес. Зверніть увагу, що контейнери не використовують порт 80 на вузлі, і немає жодних спеціальних правил NAT для маршрутизації трафіку до Podʼів. Це означає, що ви можете запустити кілька Podʼів nginx на одному й тому ж вузлі, використовуючи той же containerPort, і отримувати доступ до них з будь-якого іншого Podʼа або вузла у вашому кластері, використовуючи призначену IP адресу Podʼа. Якщо ви хочете організувати перенаправлення певного порту на хості Вузла на резервні Podʼи, ви можете це зробити — але модель мережі повинна передбачати, що вам не потрібно цього робити.

Ви можете прочитати більше про Модель мережі Kubernetes якщо вас це цікавить.

Створення Service

Отже, у нас є Podʼи, що працюють з nginx у плоскому адресному просторі, доступному для всього кластера. Теоретично, ви можете звертатися до цих Podʼів безпосередньо, але що станеться, коли вузол вийде з ладу? Podʼи загинуть разом з ним, і ReplicaSet всередині Deployment створить нові, з іншими IP-адресами. Це проблема, яку вирішує Service.

Service Kubernetes — це абстракція, яка визначає логічний набір Podʼів, що працюють десь у вашому кластері, і всі надають однакову функціональність. Коли створюється Service, йому призначається унікальна IP-адреса (яку називають clusterIP). Ця адреса привʼязана до тривалості життя Service і не змінюється, поки Service живий. Podʼи можуть бути налаштовані для звернення до Service і знати, що звʼязок з Service буде автоматично балансуватися на деякий Pod, що є членом Service.

Ви можете створити Service для ваших двох реплік nginx за допомогою kubectl expose:

kubectl expose deployment/my-nginx
service/my-nginx exposed

Це еквівалентно kubectl apply -f наступного yaml:

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx

Ця специфікація створить Service, який буде спрямований на TCP порт 80 на будь-якому Podʼі з міткою run: my-nginx і відкриє його на абстрактному порті Service (targetPort: це порт, на якому контейнер приймає трафік, port — це абстрактний порт Service, який може бути будь-яким портом, що використовується іншими Podʼами для доступу до Service). Перегляньте Service API обʼєкт, щоб побачити список підтримуваних полів у визначенні Service. Перевірте ваш Service:

kubectl get svc my-nginx
NAME       TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
my-nginx   ClusterIP   10.0.162.149   <none>        80/TCP    21s

Як згадувалося раніше, Service підтримується групою Podʼів. Ці Podʼи експонуються через EndpointSlices. Селектор Service буде оцінюватися безперервно, і результати будуть надсилатися у EndpointSlice, який підключений до Service за допомогою мітки. Коли Pod припиняє існування, він автоматично видаляється з EndpointSlices, який містить його як точку доступу. Нові Podʼи, що відповідають селектору Service, автоматично додаються до EndpointSlice для цього Service. Перевірте точки доступу та зауважте, що IP-адреси збігаються з Podʼами, створеними на першому кроці:

kubectl describe svc my-nginx
Name:                my-nginx
Namespace:           default
Labels:              run=my-nginx
Annotations:         <none>
Selector:            run=my-nginx
Type:                ClusterIP
IP Family Policy:    SingleStack
IP Families:         IPv4
IP:                  10.0.162.149
IPs:                 10.0.162.149
Port:                <unset> 80/TCP
TargetPort:          80/TCP
Endpoints:           10.244.2.5:80,10.244.3.4:80
Session Affinity:    None
Events:              <none>
kubectl get endpointslices -l kubernetes.io/service-name=my-nginx
NAME             ADDRESSTYPE   PORTS   ENDPOINTS               AGE
my-nginx-7vzhx   IPv4          80      10.244.2.5,10.244.3.4   21s

Тепер ви повинні бути в змозі звертатися до nginx Service на <CLUSTER-IP>:<PORT> з будь-якого вузла у вашому кластері. Зауважте, що IP-адреса Service є повністю віртуальною, вона ніколи не передається мережею. Якщо вам цікаво, як це працює, ви можете прочитати більше про сервіс-проксі.

Доступ до Service

Kubernetes підтримує два основні режими знаходження Service — змінні середовища та DNS. Перший режим працює "з коробки", тоді як другий вимагає додаткового модуля CoreDNS cluster addon.

Змінні середовища

Коли Pod запускається на вузлі, kubelet додає набір змінних середовища для кожного активного Service. Це створює проблему з порядком. Щоб зрозуміти чому, перевірте середовище вашого працюючого Pod nginx (назва вашого Pod буде іншою):

kubectl exec my-nginx-3800858182-jr4a2 -- printenv | grep SERVICE
KUBERNETES_SERVICE_HOST=10.0.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443

Зверніть увагу, що ваш Service не згадується. Це тому, що ви створили репліки перед створенням Service. Іншим недоліком цього є те, що планувальник може розмістити обидва Podʼи на одній машині, що призведе до припинення роботи всього Service, якщо він вийде з ладу. Ми можемо зробити це прям зараз, видаливши 2 Podʼи та очікуючи, поки Deployment їх відновить. Цього разу Service існує до реплік. Це забезпечить рівномірний розподіл ваших Podʼів на рівні планувальника (за умови рівної потужності всіх вузлів), а також правильні змінні середовища:

kubectl scale deployment my-nginx --replicas=0; kubectl scale deployment my-nginx --replicas=2;

kubectl get pods -l run=my-nginx -o wide
NAME                        READY     STATUS    RESTARTS   AGE     IP            NODE
my-nginx-3800858182-e9ihh   1/1       Running   0          5s      10.244.2.7    kubernetes-minion-ljyd
my-nginx-3800858182-j4rm4   1/1       Running   0          5s      10.244.3.8    kubernetes-minion-905m

Ви можете помітити, що Podʼи мають різні імена, оскільки вони були видалені та відтворені.

kubectl exec my-nginx-3800858182-e9ihh -- printenv | grep SERVICE
KUBERNETES_SERVICE_PORT=443
MY_NGINX_SERVICE_HOST=10.0.162.149
KUBERNETES_SERVICE_HOST=10.0.0.1
MY_NGINX_SERVICE_PORT=80
KUBERNETES_SERVICE_PORT_HTTPS=443

DNS

Kubernetes пропонує DNS-сервіс, який автоматично призначає DNS-імена іншим Serviceʼам. Ви можете перевірити, чи він працює у вашому кластері:

kubectl get services kube-dns --namespace=kube-system
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)         AGE
kube-dns   ClusterIP   10.0.0.10    <none>        53/UDP,53/TCP   8m

Далі в цьому розділі ми будемо вважати, що у вас є Service із довготривалою IP-адресою (my-nginx), і DNS-сервер, який присвоїв цій IP-адресі імʼя. Ми використовуємо надбудову CoreDNS (назва застосунку kube-dns), тому ви можете звертатися до Service з будь-якого Pod у вашому кластері, використовуючи стандартні методи (наприклад, gethostbyname()). Якщо CoreDNS не працює, ви можете увімкнути його, звірившись з README CoreDNS або інформацією з Встановлення CoreDNS. Запустімо ще один застосунок curl для перевірки цього:

kubectl run curl --image=radial/busyboxplus:curl -i --tty --rm
Waiting for pod default/curl-131556218-9fnch to be running, status is Pending, pod ready: false
Hit enter for command prompt

Потім натисніть Enter і запустіть nslookup my-nginx:

[ root@curl-131556218-9fnch:/ ]$ nslookup my-nginx
Server:    10.0.0.10
Address 1: 10.0.0.10

Name:      my-nginx
Address 1: 10.0.162.149

Захист Service

До цього моменту ми отримували доступ до сервера nginx лише всередині кластеру. Перед тим як виставити Service в Інтернет, вам потрібно переконатися, що канал звʼязку захищений. Для цього вам знадобиться:

  • Самопідписні сертифікати для https (якщо у вас немає ідентифікаційного сертифіката)
  • Сервер nginx, налаштований для використання сертифікатів
  • Secret, що робить сертифікати доступними для Pod

Ви можете отримати все це з прикладу nginx https. Це вимагає встановлення інструментів go та make. Якщо ви не хочете їх встановлювати, тоді дотримуйтесь ручних кроків, описаних нижче. Коротко:

make keys KEY=/tmp/nginx.key CERT=/tmp/nginx.crt
kubectl create secret tls nginxsecret --key /tmp/nginx.key --cert /tmp/nginx.crt
secret/nginxsecret created
kubectl get secrets
NAME                  TYPE                                  DATA      AGE
nginxsecret           kubernetes.io/tls                     2         1m

А також configmap:

kubectl create configmap nginxconfigmap --from-file=default.conf

Ви можете знайти приклад default.conf у репозиторії прикладів Kubernetes.

configmap/nginxconfigmap created
kubectl get configmaps
NAME             DATA   AGE
nginxconfigmap   1      114s

Ви можете переглянути деталі ConfigMap nginxconfigmap, використовуючи наступну команду:

kubectl describe configmap nginxconfigmap

Вихід буде схожим на:

Name:         nginxconfigmap
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
default.conf:
----
server {
        listen 80 default_server;
        listen [::]:80 default_server ipv6only=on;

        listen 443 ssl;

        root /usr/share/nginx/html;
        index index.html;

        server_name localhost;
        ssl_certificate /etc/nginx/ssl/tls.crt;
        ssl_certificate_key /etc/nginx/ssl/tls.key;

        location / {
                try_files $uri $uri/ =404;
        }
}

BinaryData
====

Events:  <none>

Ось ручні кроки, яких слід дотримуватись у випадку, якщо у вас виникли проблеми з запуском make (наприклад, у Windows):

# Створіть пару відкритий/закритий ключ
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /d/tmp/nginx.key -out /d/tmp/nginx.crt -subj "/CN=my-nginx/O=my-nginx"
# Перетворіть ключі у кодування base64
cat /d/tmp/nginx.key | base64
cat /d/tmp/nginx.crt | base64

Скористайтесь виводом з попередньої команди для створення yaml-файлу. Закодовані у base64 значення мають бути в одному рядку.

apiVersion: "v1"
kind: "Secret"
metadata:
  name: "nginxsecret"
  namespace: "default"
type: kubernetes.io/tls
data:
  tls.crt: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURIekNDQWdlZ0F3SUJBZ0lKQUp5M3lQK0pzMlpJTUEwR0NTcUdTSWIzRFFFQkJRVUFNQ1l4RVRBUEJnTlYKQkFNVENHNW5hVzU0YzNaak1SRXdEd1lEVlFRS0V3aHVaMmx1ZUhOMll6QWVGdzB4TnpFd01qWXdOekEzTVRKYQpGdzB4T0RFd01qWXdOekEzTVRKYU1DWXhFVEFQQmdOVkJBTVRDRzVuYVc1NGMzWmpNUkV3RHdZRFZRUUtFd2h1CloybHVlSE4yWXpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSjFxSU1SOVdWM0IKMlZIQlRMRmtobDRONXljMEJxYUhIQktMSnJMcy8vdzZhU3hRS29GbHlJSU94NGUrMlN5ajBFcndCLzlYTnBwbQppeW1CL3JkRldkOXg5UWhBQUxCZkVaTmNiV3NsTVFVcnhBZW50VWt1dk1vLzgvMHRpbGhjc3paenJEYVJ4NEo5Ci82UVRtVVI3a0ZTWUpOWTVQZkR3cGc3dlVvaDZmZ1Voam92VG42eHNVR0M2QURVODBpNXFlZWhNeVI1N2lmU2YKNHZpaXdIY3hnL3lZR1JBRS9mRTRqakxCdmdONjc2SU90S01rZXV3R0ljNDFhd05tNnNTSzRqYUNGeGpYSnZaZQp2by9kTlEybHhHWCtKT2l3SEhXbXNhdGp4WTRaNVk3R1ZoK0QrWnYvcW1mMFgvbVY0Rmo1NzV3ajFMWVBocWtsCmdhSXZYRyt4U1FVQ0F3RUFBYU5RTUU0d0hRWURWUjBPQkJZRUZPNG9OWkI3YXc1OUlsYkROMzhIYkduYnhFVjcKTUI4R0ExVWRJd1FZTUJhQUZPNG9OWkI3YXc1OUlsYkROMzhIYkduYnhFVjdNQXdHQTFVZEV3UUZNQU1CQWY4dwpEUVlKS29aSWh2Y05BUUVGQlFBRGdnRUJBRVhTMW9FU0lFaXdyMDhWcVA0K2NwTHI3TW5FMTducDBvMm14alFvCjRGb0RvRjdRZnZqeE04Tzd2TjB0clcxb2pGSW0vWDE4ZnZaL3k4ZzVaWG40Vm8zc3hKVmRBcStNZC9jTStzUGEKNmJjTkNUekZqeFpUV0UrKzE5NS9zb2dmOUZ3VDVDK3U2Q3B5N0M3MTZvUXRUakViV05VdEt4cXI0Nk1OZWNCMApwRFhWZmdWQTRadkR4NFo3S2RiZDY5eXM3OVFHYmg5ZW1PZ05NZFlsSUswSGt0ejF5WU4vbVpmK3FqTkJqbWZjCkNnMnlwbGQ0Wi8rUUNQZjl3SkoybFIrY2FnT0R4elBWcGxNSEcybzgvTHFDdnh6elZPUDUxeXdLZEtxaUMwSVEKQ0I5T2wwWW5scE9UNEh1b2hSUzBPOStlMm9KdFZsNUIyczRpbDlhZ3RTVXFxUlU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
  tls.key: "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ2RhaURFZlZsZHdkbFIKd1V5eFpJWmVEZWNuTkFhbWh4d1NpeWF5N1AvOE9ta3NVQ3FCWmNpQ0RzZUh2dGtzbzlCSzhBZi9WemFhWm9zcApnZjYzUlZuZmNmVUlRQUN3WHhHVFhHMXJKVEVGSzhRSHA3VkpMcnpLUC9QOUxZcFlYTE0yYzZ3MmtjZUNmZitrCkU1bEVlNUJVbUNUV09UM3c4S1lPNzFLSWVuNEZJWTZMMDUrc2JGQmd1Z0ExUE5JdWFubm9UTWtlZTRuMG4rTDQKb3NCM01ZUDhtQmtRQlAzeE9JNHl3YjREZXUraURyU2pKSHJzQmlIT05Xc0RadXJFaXVJMmdoY1kxeWIyWHI2UAozVFVOcGNSbC9pVG9zQngxcHJHclk4V09HZVdPeGxZZmcvbWIvNnBuOUYvNWxlQlkrZStjSTlTMkQ0YXBKWUdpCkwxeHZzVWtGQWdNQkFBRUNnZ0VBZFhCK0xkbk8ySElOTGo5bWRsb25IUGlHWWVzZ294RGQwci9hQ1Zkank4dlEKTjIwL3FQWkUxek1yall6Ry9kVGhTMmMwc0QxaTBXSjdwR1lGb0xtdXlWTjltY0FXUTM5SjM0VHZaU2FFSWZWNgo5TE1jUHhNTmFsNjRLMFRVbUFQZytGam9QSFlhUUxLOERLOUtnNXNrSE5pOWNzMlY5ckd6VWlVZWtBL0RBUlBTClI3L2ZjUFBacDRuRWVBZmI3WTk1R1llb1p5V21SU3VKdlNyblBESGtUdW1vVlVWdkxMRHRzaG9reUxiTWVtN3oKMmJzVmpwSW1GTHJqbGtmQXlpNHg0WjJrV3YyMFRrdWtsZU1jaVlMbjk4QWxiRi9DSmRLM3QraTRoMTVlR2ZQegpoTnh3bk9QdlVTaDR2Q0o3c2Q5TmtEUGJvS2JneVVHOXBYamZhRGR2UVFLQmdRRFFLM01nUkhkQ1pKNVFqZWFKClFGdXF4cHdnNzhZTjQyL1NwenlUYmtGcVFoQWtyczJxWGx1MDZBRzhrZzIzQkswaHkzaE9zSGgxcXRVK3NHZVAKOWRERHBsUWV0ODZsY2FlR3hoc0V0L1R6cEdtNGFKSm5oNzVVaTVGZk9QTDhPTm1FZ3MxMVRhUldhNzZxelRyMgphRlpjQ2pWV1g0YnRSTHVwSkgrMjZnY0FhUUtCZ1FEQmxVSUUzTnNVOFBBZEYvL25sQVB5VWs1T3lDdWc3dmVyClUycXlrdXFzYnBkSi9hODViT1JhM05IVmpVM25uRGpHVHBWaE9JeXg5TEFrc2RwZEFjVmxvcG9HODhXYk9lMTAKMUdqbnkySmdDK3JVWUZiRGtpUGx1K09IYnRnOXFYcGJMSHBzUVpsMGhucDBYSFNYVm9CMUliQndnMGEyOFVadApCbFBtWmc2d1BRS0JnRHVIUVV2SDZHYTNDVUsxNFdmOFhIcFFnMU16M2VvWTBPQm5iSDRvZUZKZmcraEppSXlnCm9RN3hqWldVR3BIc3AyblRtcHErQWlSNzdyRVhsdlhtOElVU2FsbkNiRGlKY01Pc29RdFBZNS9NczJMRm5LQTQKaENmL0pWb2FtZm1nZEN0ZGtFMXNINE9MR2lJVHdEbTRpb0dWZGIwMllnbzFyb2htNUpLMUI3MkpBb0dBUW01UQpHNDhXOTVhL0w1eSt5dCsyZ3YvUHM2VnBvMjZlTzRNQ3lJazJVem9ZWE9IYnNkODJkaC8xT2sybGdHZlI2K3VuCnc1YytZUXRSTHlhQmd3MUtpbGhFZDBKTWU3cGpUSVpnQWJ0LzVPbnlDak9OVXN2aDJjS2lrQ1Z2dTZsZlBjNkQKckliT2ZIaHhxV0RZK2Q1TGN1YSt2NzJ0RkxhenJsSlBsRzlOZHhrQ2dZRUF5elIzT3UyMDNRVVV6bUlCRkwzZAp4Wm5XZ0JLSEo3TnNxcGFWb2RjL0d5aGVycjFDZzE2MmJaSjJDV2RsZkI0VEdtUjZZdmxTZEFOOFRwUWhFbUtKCnFBLzVzdHdxNWd0WGVLOVJmMWxXK29xNThRNTBxMmk1NVdUTThoSDZhTjlaMTltZ0FGdE5VdGNqQUx2dFYxdEYKWSs4WFJkSHJaRnBIWll2NWkwVW1VbGc9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K"

Тепер створіть Secret, застосувавши файл:

kubectl apply -f nginxsecret.yaml
kubectl get secrets
NAME                  TYPE                                  DATA      AGE
nginxsecret           kubernetes.io/tls                     2         1m

Тепер змініть кількість реплік nginx для запуску сервера https використовуючи сертифікати з Secret та Service для експонування портів (80 та 443):

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  type: NodePort
  ports:
  - port: 8080
    targetPort: 80
    protocol: TCP
    name: http
  - port: 443
    protocol: TCP
    name: https
  selector:
    run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 1
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      volumes:
      - name: secret-volume
        secret:
          secretName: nginxsecret
      - name: configmap-volume
        configMap:
          name: nginxconfigmap
      containers:
      - name: nginxhttps
        image: bprashanth/nginxhttps:1.0
        ports:
        - containerPort: 443
        - containerPort: 80
        volumeMounts:
        - mountPath: /etc/nginx/ssl
          name: secret-volume
        - mountPath: /etc/nginx/conf.d
          name: configmap-volume

Примітні моменти про маніфест nginx-secure-app:

  • Він містить як специфікацію Deployment, так і Service в одному файлі.
  • nginx сервер обслуговує HTTP трафік на порту 80 та HTTPS трафік на порту 443, і Service nginx відкриває доступ до обох портів.
  • Кожен контейнер має доступ до ключів через том, змонтовану в /etc/nginx/ssl. Це налаштовується до запуску nginx сервера.
kubectl delete deployments,svc my-nginx; kubectl create -f ./nginx-secure-app.yaml

На цьому етапі ви можете отримати доступ до nginx сервера з будь-якого вузла.

kubectl get pods -l run=my-nginx -o custom-columns=POD_IP:.status.podIPs
    POD_IP
    [map[ip:10.244.3.5]]
node $ curl -k https://10.244.3.5
...
<h1>Welcome to nginx!</h1>

Зверніть увагу, що ми використовували параметр -k для curl на останньому етапі, тому що ми нічого не знаємо про Podʼи, що виконують nginx під час генерації сертифіката, тому ми повинні сказати curl ігнорувати невідповідність CName. Створюючи Service, ми повʼязали CName, який використовується в сертифікаті, з фактичним DNS-імʼям, що використовується Podʼами під час пошуку Service. Перевірмо це з Podʼа (для простоти використовується той же Secret, Podʼу потрібен лише nginx.crt для доступу до Service):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: curl-deployment
spec:
  selector:
    matchLabels:
      app: curlpod
  replicas: 1
  template:
    metadata:
      labels:
        app: curlpod
    spec:
      volumes:
      - name: secret-volume
        secret:
          secretName: nginxsecret
      containers:
      - name: curlpod
        command:
        - sh
        - -c
        - while true; do sleep 1; done
        image: radial/busyboxplus:curl
        volumeMounts:
        - mountPath: /etc/nginx/ssl
          name: secret-volume
kubectl apply -f ./curlpod.yaml
kubectl get pods -l app=curlpod
NAME                               READY     STATUS    RESTARTS   AGE
curl-deployment-1515033274-1410r   1/1       Running   0          1m
kubectl exec curl-deployment-1515033274-1410r -- curl https://my-nginx --cacert /etc/nginx/ssl/tls.crt
...
<title>Welcome to nginx!</title>
...

Експонування Service

Для деяких частин ваших застосунків ви можете захотіти експонувати Service на зовнішню IP-адресу. Kubernetes підтримує два способи: NodePorts та LoadBalancers. Service, створений у попередньому розділі, вже використовував NodePort, тому ваша репліка nginx HTTPS готова обслуговувати трафік з інтернету, якщо ваш вузол має публічну IP-адресу.

kubectl get svc my-nginx -o yaml | grep nodePort -C 5
  uid: 07191fb3-f61a-11e5-8ae5-42010af00002
spec:
  clusterIP: 10.0.162.149
  ports:
  - name: http
    nodePort: 31704
    port: 8080
    protocol: TCP
    targetPort: 80
  - name: https
    nodePort: 32453
    port: 443
    protocol: TCP
    targetPort: 443
  selector:
    run: my-nginx
kubectl get nodes -o yaml | grep ExternalIP -C 1
    - address: 104.197.41.11
      type: ExternalIP
    allocatable:
--
    - address: 23.251.152.56
      type: ExternalIP
    allocatable:
...

$ curl https://<EXTERNAL-IP>:<NODE-PORT> -k
...
<h1>Welcome to nginx!</h1>

Тепер створімо Service знову, використовуючи хмарний балансувальник навантаження. Змініть Type Service my-nginx з NodePort на LoadBalancer:

kubectl edit svc my-nginx
kubectl get svc my-nginx
NAME       TYPE           CLUSTER-IP     EXTERNAL-IP        PORT(S)               AGE
my-nginx   LoadBalancer   10.0.162.149   xx.xxx.xxx.xxx     8080:30163/TCP        21s
curl https://<EXTERNAL-IP> -k
...
<title>Welcome to nginx!</title>

IP-адреса в колонці EXTERNAL-IP доступна в інтернеті. CLUSTER-IP доступна тільки всередині вашого кластера/приватної хмарної мережі.

Зверніть увагу, що у AWS тип LoadBalancer створює ELB, який використовує (довге) імʼя хосту, а не IP. Воно занадто довге, щоб поміститися в стандартному виводі kubectl get svc, тому вам потрібно виконати kubectl describe service my-nginx, щоб побачити його. Ви побачите щось на кшталт:

kubectl describe service my-nginx
...
LoadBalancer Ingress:   a320587ffd19711e5a37606cf4a74574-1142138393.us-east-1.elb.amazonaws.com
...

Що далі

8.2 - Використання Source IP

Застосунки, що працюють у кластері Kubernetes, знаходять і взаємодіють один з одним та з зовнішнім світом через абстракцію Service. У цьому документі пояснюється, що відбувається з Source IP пакетів, надісланих на різні типи Service, і як ви можете змінити цю поведінку відповідно до ваших потреб.

Перш ніж ви розпочнете

Терміни

У цьому документі використовуються наступні терміни:

NAT
Трансляція мережевих адрес
Source NAT
Заміна Source IP в пакеті; у цьому документі це зазвичай означає заміну на IP-адресу вузла.
Destination NAT
Заміна Destination IP в пакеті; у цьому документі це зазвичай означає заміну на IP-адресу Pod
VIP
Віртуальна IP-адреса, така як та, що призначається кожному Service у Kubernetes
kube-proxy
Мережевий демон, який оркеструє управління віртуальними IP Service на кожному вузлі

Необхідні умови

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

У прикладах використовується невеликий вебсервер nginx, який повертає Source IP запитів, які він отримує через HTTP-заголовок. Ви можете створити його наступним чином:

kubectl create deployment source-ip-app --image=registry.k8s.io/echoserver:1.10

Результат:

deployment.apps/source-ip-app created

Цілі

  • Експонувати простий застосунок через різні типи Serviceʼів
  • Зрозуміти, як кожен тип Services обробляє Source IP NAT
  • Зрозуміти компроміси, повʼязані зі збереженням Source IP

Source IP для Service з Type=ClusterIP

Пакети, надіслані на ClusterIP зсередини кластера, ніколи не обробляються Source NAT, якщо ви використовуєте kube-proxy в iptables mode, (станартно). Ви можете запитати режим kube-proxy, отримавши http://localhost:10249/proxyMode на вузлі, де працює kube-proxy.

kubectl get nodes

Результат схожий на цей:

NAME                           STATUS     ROLES    AGE     VERSION
kubernetes-node-6jst   Ready      <none>   2h      v1.13.0
kubernetes-node-cx31   Ready      <none>   2h      v1.13.0
kubernetes-node-jj1t   Ready      <none>   2h      v1.13.0

Отримати режим проксі на одному з вузлів (kube-proxy слухає на порту 10249):

# Запустіть це в оболонці на вузлі, який хочете запитати.
curl http://localhost:10249/proxyMode

Результат:

iptables

Ви можете протестувати збереження Source IP, створивши Service над source-ip застосунку:

kubectl expose deployment source-ip-app --name=clusterip --port=80 --target-port=8080

Результат:

service/clusterip exposed
kubectl get svc clusterip

Результат схожий на:

NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
clusterip    ClusterIP   10.0.170.92   <none>        80/TCP    51s

І звернутися до ClusterIP з Pod в тому ж кластері:

kubectl run busybox -it --image=busybox:1.28 --restart=Never --rm

Результат схожий на цей:

Waiting for pod default/busybox to be running, status is Pending, pod ready: false
If you don't see a command prompt, try pressing enter.

Ви можете виконати команду всередині цього Pod:

# Запустіть це всередині терміналу "kubectl run"
ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1460 qdisc noqueue
    link/ether 0a:58:0a:f4:03:08 brd ff:ff:ff:ff:ff:ff
    inet 10.244.3.8/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::188a:84ff:feb0:26a5/64 scope link
       valid_lft forever preferred_lft forever

…потім використовувати wget для надсилання запиту до локального вебсервера

# Замініть "10.0.170.92" на IPv4-адресу сервісу "clusterip"
wget -qO - 10.0.170.92
CLIENT VALUES:
client_address=10.244.3.8
command=GET
...

client_address завжди є IP-адресою Podʼа-клієнта, незалежно від того, знаходяться Pod-клієнт і Pod-сервер на одному вузлі чи на різних вузлах.

Source IP для Service з Type=NodePort

Пакети, надіслані на Serviceʼи з Type=NodePort, типово обробляються Source NAT. Ви можете протестувати це, створивши Service NodePort:

kubectl expose deployment source-ip-app --name=nodeport --port=80 --target-port=8080 --type=NodePort

Результат:

service/nodeport exposed
NODEPORT=$(kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services nodeport)
NODES=$(kubectl get nodes -o jsonpath='{ $.items[*].status.addresses[?(@.type=="InternalIP")].address }')

Якщо ви працюєте на хмарному провайдері, можливо, вам доведеться відкрити правило брандмауера для nodes:nodeport, зазначеного вище. Тепер ви можете спробувати звернутися до сервісу ззовні кластера через вказаний вище порт вузла.

for node in $NODES; do curl -s $node:$NODEPORT | grep -i client_address; done

Результат схожий на:

client_address=10.180.1.1
client_address=10.240.0.5
client_address=10.240.0.3

Зверніть увагу, що це не правильні IP клієнтів, це внутрішні IP кластера. Це відбувається так:

  • Клієнт надсилає пакет до node2:nodePort
  • node2 замінює Source IP-адресу (SNAT) у пакеті своєю власною IP-адресою
  • node2 замінює Destination IP-адресу пакета на IP Podʼа
  • пакет спрямовується до node 1, а потім до точки доступу
  • відповідь Podʼа спрямовується назад до node2
  • відповідь Podʼа надсилається клієнту

Візуально це виглядає так:

graph LR; client(client)-->node2[Node 2]; node2-->client; node2-. SNAT .->node1[Node 1]; node1-. SNAT .->node2; node1-->endpoint(Endpoint); classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; class node1,node2,endpoint k8s; class client plain;

Схема. Source IP Type=NodePort з використанням SNAT

Щоб уникнути цього, Kubernetes має функцію зберігати Source IP-адресу клієнта. Якщо ви встановите для service.spec.externalTrafficPolicy значення Local, kube-proxy надсилає проксі-запити лише до локальних точок доступу та не пересилає трафік до інших вузлів. Цей підхід зберігає Source IP-адресу. Якщо немає локальних точок доступу, пакети, надіслані до вузла, відкидаються, тому ви можете покладатися на правильну Source IP-адресу в будь-яких правилах обробки пакетів, які ви можете застосувати до пакета, який проходить до точки доступу.

Встановіть значення поля service.spec.externalTrafficPolicy наступним чином:

kubectl patch svc nodeport -p '{"spec":{"externalTrafficPolicy":"Local"}}'

Вивід має бути наступним:

service/nodeport patched

Повторіть тест:

for node in $NODES; do curl --connect-timeout 1 -s $node:$NODEPORT | grep -i client_address; done

Вивід подібний до:

client_address=198.51.100.79

Зауважте, що ви отримали лише одну відповідь із правильною IP-адресою клієнта від одного вузла, на якому працює Pod точки доступу.

Ось що відбувається:

  • клієнт надсилає пакет на node2:nodePort, який не має кінцевих точок
  • пакет відкидається
  • клієнт надсилає пакет на node1:nodePort, який має точки доступу
  • node1 направляє пакет до точки доступу з правильною Source IP-адресою

Візуально:

graph TD; client(client)-->node1[Node 1]; client(client)-->node2[Node 2]; node1-->endpoint(Endpoint); endpoint(Endpoint)-->node1; classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; class node1,node2,endpoint k8s; class client plain;
Схема. Source IP Type=NodePort зберігає Source IP клієнта

Source IP для Service з Type=LoadBalancer

Пакети, які надсилаються на Service з Type=LoadBalancer, стандартно мають Source NAT адресацію, оскільки всі можливі для планування вузли Kubernetes у стані Ready мають право на навантаження рівномірного розподілу трафіку. Якщо пакети надходять на вузол без точки доступу, система передає їх на вузол з точкою доступу, замінюючи адресу Source IP на пакеті на IP-адресу вузла (як описано у попередньому розділі).

Ви можете перевірити це, експонувавши застосунок source-ip-app через балансувальник навантаження:

kubectl expose deployment source-ip-app --name=loadbalancer --port=80 --target-port=8080 --type=LoadBalancer

Результат виглядає наступним чином:

service/loadbalancer exposed

Виведіть IP-адреси Service:

kubectl get svc loadbalancer

Вивід подібний наступному:

NAME           TYPE           CLUSTER-IP    EXTERNAL-IP       PORT(S)   AGE
loadbalancer   LoadBalancer   10.0.65.118   203.0.113.140     80/TCP    5m

Потім надішліть запит на зовнішню IP-адресу цього Service:

curl 203.0.113.140

Вивід подібний наступному:

CLIENT VALUES:
client_address=10.240.0.5
...

Однак, якщо ви працюєте на середовищі Google Kubernetes Engine/GCE, встановлення поля service.spec.externalTrafficPolicy на значення Local змушує вузли без точок доступу Service видаляти себе зі списку вузлів, які мають право на рівномірно розподілений трафік, шляхом умисної невдачі перевірок стану.

Візуально:

IP-адреса джерела з externalTrafficPolicy

Ви можете перевірити це, встановивши анотацію:

kubectl patch svc loadbalancer -p '{"spec":{"externalTrafficPolicy":"Local"}}'

Ви повинні негайно побачити, що поле service.spec.healthCheckNodePort виділене Kubernetes:

kubectl get svc loadbalancer -o yaml | grep -i healthCheckNodePort

Вивід подібний наступному:

  healthCheckNodePort: 32122

Поле service.spec.healthCheckNodePort вказує на порт на кожному вузлі, на якому надається перевірка стану за адресою /healthz. Ви можете перевірити це:

kubectl get pod -o wide -l app=source-ip-app

Вивід подібний наступному:

NAME                            READY     STATUS    RESTARTS   AGE       IP             NODE
source-ip-app-826191075-qehz4   1/1       Running   0          20h       10.180.1.136   kubernetes-node-6jst

Використовуйте curl, щоб отримати доступ до точки доступу /healthz на різних вузлах:

# Виконайте це локально на вузлі, який ви вибрали
curl localhost:32122/healthz
1 Service Endpoints found

На іншому вузлі ви можете отримати інший результат:

# Виконайте це локально на вузлі, який ви вибрали
curl localhost:32122/healthz
No Service Endpoints Found

Контролер, який працює на панелі управління відповідає за доступ до хмарного балансувальника навантаження. Той самий контролер також виділяє HTTP перевірки стану, що вказують на цей порт/шлях на кожному вузлі. Зачекайте приблизно 10 секунд, щоб вузли без точок доступу мають невдалі перевірки стану, а потім використовуйте curl, щоб запитати IPv4-адресу балансувальника навантаження:

curl 203.0.113.140

Вивід подібний наступному:

CLIENT VALUES:
client_address=198.51.100.79
...

Міжплатформна підтримка

Тільки деякі хмарні постачальники пропонують підтримку збереження Source IP-адреси через Serviceʼи з Type=LoadBalancer. Хмарний постачальник, на якому ви працюєте, може виконати запит на надання балансувальника навантаження кількома різними способами:

  1. З проксі, що завершує зʼєднання клієнта та відкриває нове зʼєднання з вашими вузлами/точками доступу. У таких випадках Source IP-адреса завжди буде адресою хмарного балансувальника, а не адресою клієнта.

  2. З сервісом пересилання пакетів (forwarder), таким чином, що запити від клієнта, надісліна до VIP балансувальника, потрапляють на вузол з Source IP-адресою клієнта, а не на проміжний проксі.

Балансувальники навантаження у першій категорії повинні використовувати узгоджений протокол між балансувальником навантаження та бекендом для передачі реальної IP-адреси клієнта, такі як HTTP Forwarded або X-FORWARDED-FOR заголовки, або протокол проксі. Балансувальники навантаження у другій категорії можуть використовувати функціонал, описаний вище, створюючи HTTP перевірку стану, що вказує на порт, збережений у полі service.spec.healthCheckNodePort Serviceʼа.

Очищення

Видаліть Serviceʼи:

kubectl delete svc -l app=source-ip-app

Видаліть Deployment, ReplicaSet та Pod:

kubectl delete deployment source-ip-app

Що далі

8.3 - Дослідження поведінки завершення роботи для Podʼів та їх точок доступу

Коли ви підʼєднали свій застосунок до Service, дотримуючись кроків, схожих на ті, що описані в Підключення застосунків за допомогою Service, у вас є постійно працюючий, реплікований застосунок, який викритий в мережі. Цей посібник допоможе вам розглянути процес завершення роботи для Podʼів та дослідити способи впровадження належного припинення зʼєднань.

Процес завершення роботи для Podʼів та їх точок доступу

Часто виникають випадки, коли потрібно завершити роботу Podʼа — чи то для оновлення, чи то для зменшення масштабу. Для поліпшення доступності застосунку може бути важливо реалізувати правильне завершення активних зʼєднань.

У цьому посібнику пояснюється процес завершення роботи для Podʼів у звʼязку з відповідним станом точки доступу та видаленням за допомогою простого вебсервера nginx для демонстрації концепції.

Приклад процесу з завершенням роботи точки доступу

Наведений нижче приклад показує описаний у документі Завершення роботи Podʼів процес.

Допустимо, у вас є Deployment, яка складається з одної репліки nginx (для демонстраційних цілей) та Service:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      terminationGracePeriodSeconds: 120 # extra long grace period
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
        lifecycle:
          preStop:
            exec:
              # Real life termination may take any time up to terminationGracePeriodSeconds.
              # In this example - just hang around for at least the duration of terminationGracePeriodSeconds,
              # at 120 seconds container will be forcibly terminated.
              # Note, all this time nginx will keep processing requests.
              command: [
                "/bin/sh", "-c", "sleep 180"
              ]
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

Тепер створіть Pod Deployment та Service, використовуючи вищезазначені файли:

kubectl apply -f pod-with-graceful-termination.yaml
kubectl apply -f explore-graceful-termination-nginx.yaml

Після запуску Podʼа та Service ви можете отримати імʼя будь-яких повʼязаних точок доступу:

kubectl get endpointslice

Вивід подібний до цього:

NAME                  ADDRESSTYPE   PORTS   ENDPOINTS                 AGE
nginx-service-6tjbr   IPv4          80      10.12.1.199,10.12.1.201   22m

Ви можете перевірити їх статус та підтвердити, що зареєстровано одну точку доступу:

kubectl get endpointslices -o json -l kubernetes.io/service-name=nginx-service

Вивід подібний до цього:

{
    "addressType": "IPv4",
    "apiVersion": "discovery.k8s.io/v1",
    "endpoints": [
        {
            "addresses": [
                "10.12.1.201"
            ],
            "conditions": {
                "ready": true,
                "serving": true,
                "terminating": false

Тепер завершімо роботу Podʼа та перевіримо, що Pod завершується, дотримуючись налаштувань відповідного періоду завершення роботи:

kubectl delete pod nginx-deployment-7768647bf9-b4b9s

Усі Podʼи:

kubectl get pods

Вивід подібний до цього:

NAME                                READY   STATUS        RESTARTS      AGE
nginx-deployment-7768647bf9-b4b9s   1/1     Terminating   0             4m1s
nginx-deployment-7768647bf9-rkxlw   1/1     Running       0             8s

Ви бачите, що новий Pod був запланований.

Поки створюється нова точка доступу для нового Podʼа, стара точка доступу все ще знаходиться у стані завершення:

kubectl get endpointslice -o json nginx-service-6tjbr

Вивід подібний до цього:

{
    "addressType": "IPv4",
    "apiVersion": "discovery.k8s.io/v1",
    "endpoints": [
        {
            "addresses": [
                "10.12.1.201"
            ],
            "conditions": {
                "ready": false,
                "serving": true,
                "terminating": true
            },
            "nodeName": "gke-main-default-pool-dca1511c-d17b",
            "targetRef": {
                "kind": "Pod",
                "name": "nginx-deployment-7768647bf9-b4b9s",
                "namespace": "default",
                "uid": "66fa831c-7eb2-407f-bd2c-f96dfe841478"
            },
            "zone": "us-central1-c"
        },
        {
            "addresses": [
                "10.12.1.202"
            ],
            "conditions": {
                "ready": true,
                "serving": true,
                "terminating": false
            },
            "nodeName": "gke-main-default-pool-dca1511c-d17b",
            "targetRef": {
                "kind": "Pod",
                "name": "nginx-deployment-7768647bf9-rkxlw",
                "namespace": "default",
                "uid": "722b1cbe-dcd7-4ed4-8928-4a4d0e2bbe35"
            },
            "zone": "us-central1-c"

Це дозволяє застосункам передавати свій стан під час завершення, а клієнтам (таким як балансувальники навантаження) реалізувати функціональність завершення зʼєднань. Ці клієнти можуть виявляти завершальні точки доступу та реалізувати спеціальну логіку для них.

У Kubernetes точки доступу, які завершуються, завжди мають свій статус ready встановлений як false. Це потрібно для забезпечення зворотної сумісності, щоб наявні балансувальники навантаження не використовували їх для звичайного трафіку. Якщо потрібно припинення обробки трафіку на Podʼі, що завершує роботу, фактична готовність може бути перевірені як умова serving.

Після видалення Podʼа, стару точку доступу також буде видалено.

Що далі