текст перекладу
Привіт, я Neidson і сьогодні я розповім про те, чому управління життєвим циклом розгортання Kubernetes (K8s) стає все більш важливим. У Kubernetes розуміння того, як контейнери (pods) коректно завершують свою роботу, може стати вирішальним для успішного розгортання і для уникнення розчарування користувачів. Цей практичний посібник розгляне три ключові концепції: preStop
хуки (hooks), terminationGracePeriodSeconds
та поведінку при коректному завершенні роботи.
Необхідні умови
- Працюючий Kubernetes кластер (Kind)
- Встановлений
kubectl
- Встановлений
docker
- Базове розуміння концепцій Kubernetes
Проблема: чому важливе коректне завершення роботи
Уявіть, що ви запускаєте веб-сервер, який обробляє кілька запитів. Якщо Kubernetes раптово завершує роботу контейнера, поточні запити можуть не виконатися, що призведе до поганого досвіду для користувачів. Саме тут на допомогу приходять механізми коректного завершення роботи.
Розуміння процесу завершення роботи контейнера
Коли Kubernetes вирішує завершити роботу контейнера, він слідує певній послідовності:
- Статус контейнера змінюється на "Terminating"
- Виконується
preStop
хук (якщо він налаштований) - Надсилається сигнал SIGTERM головному процесу
- Чекаємо на
terminationGracePeriodSeconds
- Якщо процес не завершився, надсилається сигнал SIGKILL
Розглянемо кожен компонент на практичних прикладах.
Практичний приклад: налаштування тестового додатку
Спочатку створимо простий веб-сервер, який симулює довготривалі запити:
from flask import Flask, request
import time
import signal
import sys
import logging
import datetime
# Налаштування логування для відображення позначок часу
logging.basicConfig(
format='%(asctime)s %(message)s',
level=logging.INFO,
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)
app = Flask(__name__)
requests_in_progress = 0
@app.before_request
def before_request():
global requests_in_progress
requests_in_progress += 1
logger.info(f"Starting request. Active requests: {requests_in_progress}")
@app.after_request
def after_request(response):
global requests_in_progress
requests_in_progress -= 1
logger.info(f"Completed request. Remaining requests: {requests_in_progress}")
return response
@app.route('/slow-request')
def slow_request():
request_id = datetime.datetime.now().strftime('%H:%M:%S')
logger.info(f"Starting slow request {request_id}")
# Симулюємо довготривалий запит
for i in range(10):
time.sleep(1)
logger.info(f"Request {request_id}: {i+1} seconds completed")
logger.info(f"Completed slow request {request_id}")
return f'Request {request_id} completed\n'
def graceful_shutdown(signum, frame):
shutdown_time = datetime.datetime.now().strftime('%H:%M:%S')
logger.info(f"[{shutdown_time}] Received SIGTERM signal")
logger.info(f"[{shutdown_time}] Waiting for {requests_in_progress} requests to complete...")
# Чекаємо, поки запити завершаться
while requests_in_progress > 0:
time.sleep(1)
logger.info(f"Remaining requests: {requests_in_progress}")
logger.info("All requests completed, shutting down gracefully")
sys.exit(0)
signal.signal(signal.SIGTERM, graceful_shutdown)
if __name__ == '__main__':
logger.info("Starting Flask application...")
app.run(host='0.0.0.0', port=8080)
Збережіть цей код як app.py
і створіть файл Dockerfile
:
FROM python:3.9-slim
WORKDIR /app
COPY app.py .
RUN pip install flask
CMD ["python", "app.py"]
Тепер збудуйте образ та завантажте його в Kind:
docker build -t web-server .
kind load docker-image web-server:latest
Налаштування ресурсів Kubernetes
Щоб коректно протестувати наш додаток, нам потрібно створити два ресурси Kubernetes:
- Деплоймент для керування нашими контейнерами
2.
текст перекладу
## Сервіс для публікації нашого застосунку
Давайте створимо їх крок за кроком:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
replicas: 1
selector:
matchLabels:
app: web-server
template:
metadata:
labels:
app: web-server
spec:
terminationGracePeriodSeconds: 15 # <----- тут
containers:
- name: web-server
image: web-server:latest
imagePullPolicy: Never
ports:
- containerPort: 8080
lifecycle:
preStop: # <----- preStop тут
exec:
command: ["/bin/sh", "-c", "sleep 5"] # <----- чекає 5 секунд
Застосуйте конфігурацію вище, набравши:
kubectl apply -f deployment.yaml
У цьому прикладі preStop
хук чекає 5 секунд перед тим, як дозволити процесу завершення продовжитися. Це дає час для видалення сервісу з балансувальників навантаження та завершення запитів, що обробляються. У файлі конфігурації деплойменту ви також знайдете поле terminationGracePeriodSeconds
, яке вказує, скільки часу Kubernetes має чекати після надсилання сигналу SIGTERM, перш ніж насильно зупинити контейнер за допомогою SIGKILL. За замовчуванням це 15 секунд, але ви можете налаштувати це значення залежно від потреб вашого застосунку.
Створення Сервісу
Тепер давайте створимо Сервіс для публікації нашого деплойменту:
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: web-server
spec:
selector:
app: web-server
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: NodePort
Застосуйте конфігурацію вище, набравши:
kubectl apply -f service.yaml
Тестування коректного завершення роботи
Давайте протестуємо нашу налаштування:
- Отримайте NodePort та IP-адресу:
export NODE_PORT=$(kubectl get svc web-server -o jsonpath='{.spec.ports[0].nodePort}')
export NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
- Запустіть довготривалий запит:
curl http://$NODE_IP:$NODE_PORT/slow-request &
- Виконайте наступні команди в окремому терміналі:
# Отримайте ім'я pod
POD_NAME=$(kubectl get pods -l app=web-server -o jsonpath='{.items[0].metadata.name}')
# Переглядайте логи в реальному часі
kubectl logs $POD_NAME -f
Виконайте наступні команди в третьому терміналі для видалення Pod і повернення до попереднього терміналу:
POD_NAME=$(kubectl get pods -l app=web-server -o jsonpath='{.items[0].metadata.name}')
exec kubectl delete pod $POD_NAME --wait=false
- Спостерігайте за послідовністю в терміналі, який реєструє логи:
- Виконується
preStop
хук іsleep 5
(чекає 5 секунд) - Застосунок отримує сигнал SIGTERM
- Застосунок завершує запит
- Pod завершується
Кращі практики
- Завжди реалізуйте правильну обробку сигналів у вашому застосунку
- Встановлюйте відповідні таймаути в HTTP сервері вашого застосунку
- Налаштовуйте
preStop
хуки для врахування зливу балансувальника навантаження - Налаштовуйте
terminationGracePeriodSeconds
залежно від часу на обробку найдовших запитів вашого застосунку
Поширені помилки
- Не враховувати запити, що знаходяться в обробці
- Встановлення
terminationGracePeriodSeconds
занадто низьким - Ігнорування сигналів SIGTERM у вашому застосунку
- Не реалізування
preStop
хуків при використанні service mesh або балансувальників навантаження
Розуміння цих концепцій забезпечить коректну обробку завершення роботи вашими застосунками, що підвищить досвід користувачів під час розгортання та масштабування. Не забувайте ретельно тестувати поведінку завершення роботи в тестовому середовищі перед впровадженням у виробництво.
Успіхів у програмуванні!
Перекладено з: Understanding Pod Lifecycle in Kubernetes: A Hands-on Guide to Graceful Shutdowns