بايثون لأتمتة DevOps

التعامل مع JSON وYAML وملفات الإعداد

18 دقيقة الدرس 3 من 28

التعامل مع JSON وYAML وملفات الإعداد

تعتمد سكريبتات DevOps في نجاحها أو فشلها على قدرتها في قراءة البيانات المهيكلة والتحقق منها وإصدارها. مانيفستات Kubernetes، ومتغيرات Terraform، وتعريفات خطوط CI، وأعلام الميزات، ومخزون Ansible — كلها YAML أو JSON. إتقان التعامل مع هذه الصيغ، بما في ذلك أوضاع الفشل في الإنتاج، هو ما يميّز المهندس الموثوق عمّن تُفسد سكريبته إعداد الإنتاج بصمت.

JSON: لغة API العالمية

وحدة json في Python جزء من المكتبة القياسية وتغطي 95٪ من عمل JSON الفعلي. العمليات الأساسية: json.loads() (نص إلى dict)، وjson.dumps() (dict إلى نص)، وjson.load() (مقبض ملف إلى dict)، وjson.dump() (dict إلى مقبض ملف).

import json from pathlib import Path # --- تحليل JSON من نص (شائع: جسم استجابة API) --- raw = '{"service": "api-gateway", "replicas": 3, "healthy": true}' cfg = json.loads(raw) print(cfg["replicas"]) # 3 (عدد صحيح وليس نصًا) # --- قراءة ملف JSON --- config_path = Path("/etc/myapp/config.json") with config_path.open() as fh: config = json.load(fh) # --- إصدار JSON (منسق، مرتب الأمراء) --- output = json.dumps(config, indent=2, sort_keys=True) print(output) # --- كتابة ملف JSON بطريقة ذرية --- tmp = config_path.with_suffix(".json.tmp") with tmp.open("w") as fh: json.dump(config, fh, indent=2) tmp.replace(config_path) # إعادة تسمية ذرية — لا تترك ملفًا مكتوبًا جزئيًا
لا تكتب ملفات الإعداد بمجرد open+write مباشرة. إذا توقف السكريبت في منتصف الكتابة، ستترك ملفًا مجتزءًا يُفسد الخدمة عند إعادة التشغيل. اكتب دائمًا إلى ملف مؤقت في نفس المجلد، ثم استخدم rename() أو Path.replace() في Python. على Linux، إعادة التسمية في نفس نظام الملفات عملية ذرية على مستوى النواة.

YAML: صيغة إعداد المكدس السحابي الأصلي

YAML ليست في المكتبة القياسية. الاختيار الإنتاجي هو PyYAML (اسم الاستيراد yaml). استخدم دائمًا yaml.safe_load() — لا تستخدم أبدًا yaml.load() بدون وسيط Loader صريح، لأن المُحمِّل الافتراضي قادر على تنفيذ كود Python عشوائي مضمّن في ملف YAML، وهو ثغرة تنفيذ تعليمات برمجية عن بُعد خطيرة.

# pip install pyyaml import yaml from pathlib import Path # --- قراءة مانيفست Kubernetes --- manifest_text = Path("deployment.yaml").read_text() manifest = yaml.safe_load(manifest_text) name = manifest["metadata"]["name"] image = manifest["spec"]["template"]["spec"]["containers"][0]["image"] print(f"نشر {name} باستخدام صورة {image}") # --- قراءة ملف بمستندات YAML متعددة (فاصل ---) --- with open("multi-doc.yaml") as fh: docs = list(yaml.safe_load_all(fh)) # يُعيد مولّدًا — استهلكه # --- إصدار YAML --- data = { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "api-gateway", "namespace": "production"}, "spec": {"replicas": 3}, } print(yaml.dump(data, default_flow_style=False, sort_keys=False))
احتفظ بترتيب المفاتيح في مخرجات YAML. مرر sort_keys=False إلى yaml.dump(). Kubernetes وHelm لا يشترطان ترتيبًا معينًا، لكن المهندسين يتوقعون أن يأتي apiVersion قبل spec. الترتيب الأبجدي يجعل مقارنات الفروق (diffs) مزعجة ومراجعات الكود أصعب.

مشكلة النرويج في YAML وأخطاء شائعة أخرى

لدى YAML عدة مفاجآت تحليلية أسببت في أعطال إنتاجية فعلية. أشهرها مشكلة النرويج: يحلّل YAML 1.1 (الذي لا يزال PyYAML يستخدمه افتراضيًا) الكلمة المجردة NO كقيمة منطقية False. رموز الدول في مخزون Ansible قد تتحول صامتة إلى False. أحِط دائمًا النصوص التي تشبه القيم المنطقية أو null بعلامات اقتباس: "NO"، "yes"، "null"، "true"، "on".

مصائد أخرى: مفتاح العدد الصحيح المجرد (123: value) يصبح مفتاح Python من نوع int لا نصًا، مما يُفشل البحث dict["123"]. والأعداد الثمانية (0777) تُحلَّل كأعداد صحيحة. والتواريخ (2024-01-15) تصبح كائنات datetime.date في Python.

JSON and YAML data flow in a DevOps script JSON File config.json YAML File manifest.yaml TOML / INI pyproject.toml Parse json / yaml / tomllib Validate jsonschema / Pydantic Use Python dict Source Load Validate Consume ValidationError → فشل سريع، تسجيل والخروج
تدفق بيانات الإعداد المهيكلة: تحميل من الملف، تحليل إلى dict في Python، التحقق من المخطط، ثم الاستخدام — مع الفشل السريع عند وجود مدخلات خاطئة.

التحقق من الإعداد باستخدام jsonschema وPydantic

تحليل ملف YAML بدون التحقق منه يعني أن السكريبت سيفشل لاحقًا بخطأ KeyError أو TypeError مبهم في عمق منطق الأعمال. تحقق عند الحدود — مباشرة بعد التحميل وقبل أي معالجة. خياران يهيمنان في شركات التقنية الكبرى:

  • jsonschema — يُحقق من أي dict مقابل تعريف JSON Schema؛ يعمل لبيانات JSON وYAML معًا؛ تبعية خفيفة الوزن.
  • Pydantic v2 — يُعرّف النماذج كفئات Python؛ يمنحك سمات مكتوبة وقيمًا افتراضية ورسائل خطأ غنية؛ مفضل للإعدادات المعقدة وعندما تحتاج إلى إكمال تلقائي في IDE.
# pip install pydantic from pydantic import BaseModel, Field, ValidationError from typing import List import yaml, sys class ContainerSpec(BaseModel): image: str port: int = Field(gt=0, lt=65536) env_vars: List[str] = [] class DeployConfig(BaseModel): service: str namespace: str = "default" replicas: int = Field(ge=1, le=100) container: ContainerSpec raw = yaml.safe_load(open("deploy.yaml")) try: cfg = DeployConfig(**raw) except ValidationError as exc: print("فشل التحقق من الإعداد:", file=sys.stderr) for err in exc.errors(): print(f" {err['loc']}: {err['msg']}", file=sys.stderr) sys.exit(1) # الآن cfg.container.image مضمون أنه str — آمن للاستخدام print(f"نشر {cfg.service} x{cfg.replicas} في {cfg.namespace}")
افشل بسرعة وبصوت عالٍ. خطأ التحقق من الإعداد الذي يُكتشف عند بدء التشغيل يُوفر ساعات من التصحيح لفشل متتالٍ في الساعة الثالثة صباحًا. Pydantic يطبع المسار الكامل للحقل الخاطئ (مثل container > port) ورسالة مقروءة. أرسل هذه الرسالة إلى نظام التسجيل لديك قبل الخروج.

الإعداد المبني على متغيرات البيئة: منهجية Twelve-Factor

خدمات الإنتاج لا ينبغي أن تقرأ الأسرار من ملفات YAML مُدرجة في git. نمط Twelve-Factor App يخزن بيانات الاعتماد وعناوين URL لقواعد البيانات ومفاتيح API في متغيرات البيئة، ويقرأ ملفات الإعداد فقط للإعدادات غير السرية القابلة للإصدار. os.environ في Python وحزمة python-dotenv تتولى هذا بشكل نظيف.

import os from dotenv import load_dotenv # pip install python-dotenv # تحميل ملف .env إلى os.environ (لا تأثير إذا غاب الملف — آمن في الإنتاج) load_dotenv() DB_URL = os.environ["DATABASE_URL"] # KeyError إذا غاب — مقصود API_KEY = os.environ.get("API_KEY", "") # اختياري مع قيمة افتراضية LOG_LVL = os.environ.get("LOG_LEVEL", "INFO").upper() # دمج: إعداد أساسي من YAML + أسرار من متغيرات البيئة import yaml with open("base_config.yaml") as fh: config = yaml.safe_load(fh) config["db"]["url"] = DB_URL # تجاوز placeholder في YAML بالسر الحقيقي config["api_key"] = API_KEY
استخدم os.environ["KEY"] (لا .get()) للأسرار المطلوبة. إذا كان متغير مطلوب غائبًا، تريد KeyError صاخبًا عند بدء التشغيل، لا نصًا فارغًا صامتًا يتسبب في فشل مصادقة غامض بعد 200 طلب. احتفظ بـ.get("KEY", default) للإعدادات الاختيارية فعلًا.

TOML: صيغة الإعداد الخاصة بنظام Python

منذ Python 3.11، أصبحت tomllib في المكتبة القياسية (للقراءة فقط). وهي صيغة pyproject.toml ويُستخدم بصورة متزايدة لإعدادات الأدوات. إذا كنت بحاجة للكتابة بتنسيق TOML، ثبّت tomli-w.

إطار القرار الكامل: استخدم JSON عند التواصل مع APIs أو الآلات؛ استخدم YAML لـKubernetes وAnsible وخطوط CI (بشر + آلات)؛ استخدم TOML لبيانات مشاريع Python وإعدادات الأدوات؛ استخدم متغيرات البيئة للأسرار والتجاوزات أثناء التشغيل.