Python Django Eureka Client在Kubernetes集群中无法从Eureka Server注销
本地环境下,启动Django项目能正常注册到Spring Eureka Server,按CTRL+C停止时也能正常注销;但在Kubernetes集群中,通过kubectl apply -f kub8s-media.yaml启动Pod可成功注册,执行kubectl delete -f kub8s-media.yaml删除Pod或kubectl rollout restart deployment kub8s-deploy重启Deployment时,Django实例无法从Eureka Server注销。
相关配置文件
Dockerfile
FROM python RUN mkdir /backend-media COPY . /backend-media WORKDIR /backend-media ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 EXPOSE 8001 RUN pip install -r requirements.txt CMD ["python", "manage.py", "runserver", "0.0.0.0:8001"]
kub8s-media.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: backend-media-deploy spec: selector: matchLabels: app: backend-media replicas: 1 template: metadata: labels: app: backend-media spec: containers: - name: backend-media-app ports: - containerPort: 8001 image: aalfaa00/media:latest --- apiVersion: v1 kind: Service metadata: name: backend-media-svc labels: app: backend-media spec: type: NodePort ports: - port: 8001 protocol: TCP selector: app: backend-media
eureka.py
import random import string import os import py_eureka_client.eureka_client as eureka_client N = 32 res = ''.join(random.choices(string.ascii_lowercase + string.digits, k=N)) server_port = 8001 def eureka_init(): if os.environ.get("RUN_MAIN"): eureka_client.init(eureka_server="http://192.168.49.2:32451", app_name="service", instance_port=server_port, instance_id=("media:" + str(res))) print("Eureka client is running") def stop_eureka(): if os.environ.get("RUN_MAIN"): eureka_client.stop() print("Stopping Eureka client")
manage.py
import os import sys from media_service.eureka import eureka_init, stop_eureka def main(): """Run administrative tasks.""" os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'media_service.settings') if os.environ.get('RUN_MAIN'): print('run') eureka_init() try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc execute_from_command_line(sys.argv) stop_eureka() if __name__ == '__main__': main()
问题原因及解决方案
1. Kubernetes信号处理缺失
本地按CTRL+C发送SIGINT信号,Django runserver会优雅退出并执行stop_eureka();但Kubernetes删除Pod时默认发送SIGTERM信号,runserver未处理该信号,进程直接被杀死,导致注销逻辑无法执行。
解决方法:
在manage.py中添加信号监听,捕获SIGTERM和SIGINT触发注销:
import os import sys import signal from media_service.eureka import eureka_init, stop_eureka def handle_shutdown(signum, frame): print(f"收到信号{signum},开始注销Eureka实例...") stop_eureka() sys.exit(0) # 注册信号处理器 signal.signal(signal.SIGTERM, handle_shutdown) signal.signal(signal.SIGINT, handle_shutdown) def main(): os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'media_service.settings') if os.environ.get('RUN_MAIN'): print('run') eureka_init() try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc execute_from_command_line(sys.argv) stop_eureka() if __name__ == '__main__': main()
2. Django runserver的阻塞特性
execute_from_command_line(sys.argv)执行runserver时是阻塞状态,后续的stop_eureka()仅在进程正常退出时执行,而Kubernetes的强制终止会跳过这步。上述信号处理可解决此问题。
3. 优化Eureka Server的实例剔除逻辑
即使注销失败,可通过调整Eureka Server配置缩短心跳间隔和过期时间,自动清理无响应实例:
在Eureka Server配置文件中添加:
eureka.instance.lease-renewal-interval-in-seconds=10 eureka.instance.lease-expiration-duration-in-seconds=30
超过30秒未收到心跳的实例会被自动剔除。
4. 替换为生产级WSGI服务器
Django runserver是开发环境工具,生产环境建议用Gunicorn/UWSGI,这类服务器支持优雅退出:
- 修改Dockerfile的CMD:
CMD ["gunicorn", "--bind", "0.0.0.0:8001", "--workers", "2", "media_service.wsgi:application"]
- 创建启动脚本
start.sh:
#!/bin/bash python -c "from media_service.eureka import eureka_init; eureka_init()" gunicorn --bind 0.0.0.0:8001 --workers 2 media_service.wsgi:application python -c "from media_service.eureka import stop_eureka; stop_eureka()"
- 在Dockerfile中添加脚本权限:
RUN chmod +x start.sh CMD ["./start.sh"]
内容的提问来源于stack exchange,提问作者Ismoil

