Розуміння життєвого циклу контейнера в Kubernetes: Практичний посібник по коректному завершенню роботи

текст перекладу
pic

Привіт, я Neidson і сьогодні я розповім про те, чому управління життєвим циклом розгортання Kubernetes (K8s) стає все більш важливим. У Kubernetes розуміння того, як контейнери (pods) коректно завершують свою роботу, може стати вирішальним для успішного розгортання і для уникнення розчарування користувачів. Цей практичний посібник розгляне три ключові концепції: preStop хуки (hooks), terminationGracePeriodSeconds та поведінку при коректному завершенні роботи.

Необхідні умови

  • Працюючий Kubernetes кластер (Kind)
  • Встановлений kubectl
  • Встановлений docker
  • Базове розуміння концепцій Kubernetes

Проблема: чому важливе коректне завершення роботи

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

Розуміння процесу завершення роботи контейнера

Коли Kubernetes вирішує завершити роботу контейнера, він слідує певній послідовності:

  1. Статус контейнера змінюється на "Terminating"
  2. Виконується preStop хук (якщо він налаштований)
  3. Надсилається сигнал SIGTERM головному процесу
  4. Чекаємо на terminationGracePeriodSeconds
  5. Якщо процес не завершився, надсилається сигнал 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:

  1. Деплоймент для керування нашими контейнерами
    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

Тестування коректного завершення роботи

Давайте протестуємо нашу налаштування:

  1. Отримайте 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}')
  1. Запустіть довготривалий запит:
curl http://$NODE_IP:$NODE_PORT/slow-request &
  1. Виконайте наступні команди в окремому терміналі:
# Отримайте ім'я 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
  1. Спостерігайте за послідовністю в терміналі, який реєструє логи:
  • Виконується preStop хук і sleep 5 (чекає 5 секунд)
  • Застосунок отримує сигнал SIGTERM
  • Застосунок завершує запит
  • Pod завершується

Кращі практики

  1. Завжди реалізуйте правильну обробку сигналів у вашому застосунку
  2. Встановлюйте відповідні таймаути в HTTP сервері вашого застосунку
  3. Налаштовуйте preStop хуки для врахування зливу балансувальника навантаження
  4. Налаштовуйте terminationGracePeriodSeconds залежно від часу на обробку найдовших запитів вашого застосунку

Поширені помилки

  1. Не враховувати запити, що знаходяться в обробці
  2. Встановлення terminationGracePeriodSeconds занадто низьким
  3. Ігнорування сигналів SIGTERM у вашому застосунку
  4. Не реалізування preStop хуків при використанні service mesh або балансувальників навантаження

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

Успіхів у програмуванні!

Перекладено з: Understanding Pod Lifecycle in Kubernetes: A Hands-on Guide to Graceful Shutdowns

Leave a Reply

Your email address will not be published. Required fields are marked *