أساسيات لينكس

متغيرات البيئة والضبط

22 دقيقة الدرس 9 من 26

متغيرات البيئة والضبط

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

ما هي متغيرات البيئة وكيف تعمل

حين تُفرّع النواة عملية جديدة، تمرر إليها شيئين: وسيطاتها (argv) وبيئتها (envp) — مصفوفة من سلاسل KEY=VALUE منتهية بـ null. يمكن للعملية الابنة قراءة أي من هذه السلاسل، وترث نسخة كاملة من بيئة الأب. والأهم: العملية الابنة لا ترى سوى نسختها الخاصة؛ التغييرات التي تُجريها الابنة لا تعود للأعلى إلى الأب. هذا مبدأ تصميمي أساسي في Unix: البيئة تُورَث للأسفل، ولا تُورَث أبداً للأعلى.

اقرأ متغيرات البيئة وتحكم فيها بهذه الأوامر الأساسية:

# طباعة جميع متغيرات البيئة في الصدفة الحالية printenv # طباعة متغير واحد printenv HOME echo $HOME # الصدفة تُوسّع المتغير # تعيين متغير في جلسة الصدفة الحالية (لا يُصدَّر للعمليات الفرعية) MY_VAR=hello echo $MY_VAR # hello # تصديره حتى ترثه العمليات الفرعية export MY_VAR=hello export DB_HOST=postgres.internal # إلغاء تعيين متغير unset MY_VAR # تشغيل أمر واحد مع تجاوز مؤقت للبيئة # (لا يُعدّل الصدفة الحالية) DB_HOST=staging.internal ./my-app # رؤية البيئة الفعلية لعملية قيد التشغيل (استبدل PID بالرقم الفعلي) cat /proc/12345/environ | tr '\0' '\n'
فكرة جوهرية: VAR=value command (بدون export) يُعيّن المتغير لاستدعاء ذلك الأمر الواحد فقط. إنها أنظف طريقة للاختبار بضبط مختلف دون تلويث جلسة الصدفة. السكريبتات الإنتاجية تستخدم هذا النمط بكثرة.

متغير PATH — أهم متغير في أي نظام

PATH قائمة مجلدات مفصولة بنقطتين تبحث فيها الصدفة، بالترتيب، حين تكتب اسم أمر بدون مسار. حين تكتب docker، تمشي الصدفة عبر كل مجلد في PATH وتُنفّذ أول تطابق تجده. إن لم يُوجد تطابق، ستحصل على command not found.

# فحص PATH الحالي echo $PATH # /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games # اعرف أي ملف ثنائي سيُنفَّذ بالضبط لاسم أمر معين which python3 # /usr/bin/python3 type python3 # python3 is /usr/bin/python3 command -v python3 # /usr/bin/python3 (متوافق مع POSIX؛ استخدمه في السكريبتات) # إضافة مجلد في مقدمة PATH (أولوية أعلى) export PATH="/opt/myapp/bin:$PATH" # مواقع شائعة ستضيفها في مسيرة DevOps: # /usr/local/bin -- تثبيتات homebrew/snap/مخصصة # $HOME/.local/bin -- أدوات مثبتة للمستخدم (pip install --user الخ) # $HOME/go/bin -- ثنائيات Go # $HOME/.cargo/bin -- ثنائيات Rust (cargo install) # /snap/bin -- حزم Ubuntu Snap
مزلق إنتاجي — ترتيب PATH مهم: إضافة مجلد في المقدمة يعني أن ملفك الثنائي المخصص يُخفي ملفاً ثنائياً للنظام بالاسم نفسه. هذا مقصود حين تحتاج إصداراً أحدث، لكنه خطير إن تم بإهمال. حادثة شائعة: مطور يُثبّت curl محلياً يحتوي على رايات debug ويُضيف مجلده في المقدمة، ثم خدمة systemd تستدعي curl فتلتقط بناء debug وتفشل بصمت. دائماً اعرف أي ملف ثنائي يفوز بـ which <command>.

ملفات الإعداد وأسبقية التكوين

تقرأ الصدفة ملفات تكوين مختلفة حسب طريقة تشغيلها. الخطأ في هذا مصدر كثير من أخطاء "تعمل على جهازي، تنكسر في cron / CI / SSH". هناك محوران:

  • صدفة تسجيل الدخول مقابل صدفة غير تسجيل الدخول: صدفة تسجيل الدخول هي أول جلسة صدفة تبدأ حين تُسجّل الدخول — مثل تسجيل دخول SSH، أو su - username. صدفة غير تسجيل الدخول هي أي صدفة لاحقة — مثل فتح تبويب طرفية جديد، أو تشغيل bash من داخل سكريبت آخر.
  • صدفة تفاعلية مقابل غير تفاعلية: الصدفة التفاعلية لديها طرفية متصلة وتستطلع المستخدم. الصدفة غير التفاعلية تُشغّل سكريبتاً وتخرج — هذا ما تستخدمه مهام cron ومُشغّلات CI.
Shell Profile Load Order bash Profile Load Order Login Interactive Non-Login Interactive Non-Interactive Script / cron / CI File Read Order /etc/profile reads /etc/profile ~/.bash_profile reads ~/.bash_profile ~/.bashrc reads ~/.bashrc $BASH_ENV (if set) reads $BASH_ENV ~/.bash_logout (exit) on logout only
ترتيب تحميل ملفات الإعداد حسب نوع الجلسة. الصدفات غير التفاعلية (cron وCI) لا تُحمّل ~/.bashrc — مصدر شائع لأخطاء "تعمل في الطرفية، تفشل في cron".

القواعد العملية لمكان وضع كل شيء:

  • /etc/profile و/etc/profile.d/*.sh: إعدادات على مستوى النظام تُطبَّق على كل مستخدم عند تسجيل الدخول. في الإنتاج، تكتب أدوات إدارة التكوين (Ansible وPuppet) هنا لضبط إضافات PATH أو إعدادات البروكسي على مستوى الأسطول. لا تضع أسراراً هنا أبداً.
  • ~/.bash_profile (أو ~/.profile): لكل مستخدم، عند تسجيل الدخول فقط. المكان المعياري لتعيين وتصدير متغيرات البيئة التي تحتاجها الأدوات في كل مكان (مثل GOPATH وJAVA_HOME وNVM_DIR). بالاتفاق، يُضمّن هذا الملف ~/.bashrc حتى تستفيد منه الصدفات التفاعلية أيضاً.
  • ~/.bashrc: لكل مستخدم، للصدفة التفاعلية غير صدفة تسجيل الدخول. الأسماء المستعارة، ووظائف الصدفة، وتخصيص سطر الأوامر، والإكمالات تذهب هنا. لا تضع أوامر طويلة التشغيل أو عمليات حسابية ثقيلة هنا — يُشغَّل عند كل تبويب طرفية جديد.
  • /etc/environment: ملف يقرأه PAM. أزواج مفتاح-قيمة، بدون صياغة صدفة. المتغيرات المُعيَّنة هنا متاحة لجميع العمليات التي يبدأها PAM (تسجيل الدخول، SSH، الجلسات الرسومية) بصرف النظر عن الصدفة التي يستخدمونها. مثالي لـLANG وTZ وإعدادات البروكسي أو رايات الميزات التي يجب أن تراها كل عملية على المضيف.
# /etc/environment -- مفتاح=قيمة بسيطة، بدون كلمة export، بدون توسيع صدفة LANG=en_US.UTF-8 TZ=UTC HTTP_PROXY=http://proxy.internal:3128 HTTPS_PROXY=http://proxy.internal:3128 NO_PROXY=localhost,127.0.0.1,.internal # ~/.bash_profile -- يُضمّن ~/.bashrc ثم يُعيّن صادرات وقت تسجيل الدخول [[ -f ~/.bashrc ]] && source ~/.bashrc export GOPATH="$HOME/go" export PATH="$GOPATH/bin:$HOME/.local/bin:$PATH" export JAVA_HOME="/usr/lib/jvm/java-21-openjdk-amd64" export EDITOR=vim export PAGER=less # ~/.bashrc -- تفاعلي فقط: أسماء مستعارة، سطر أوامر، إكمالات alias ll='ls -lah --color=auto' alias k='kubectl' alias tf='terraform' # سطر أوامر Git (يُظهر اسم الفرع في PS1) parse_git_branch() { git branch 2>/dev/null | sed -n 's/\* //p'; } export PS1='\u@\h:\w \[\e[32m\]$(parse_git_branch)\[\e[0m\]$ '

تطبيق التغييرات بدون إعادة التشغيل

بعد تعديل ملف الإعداد، لا تُحدَّث جلسة الصدفة الحالية تلقائياً. لديك خياران:

  • source ~/.bashrc (أو الاختصار بنقطة: . ~/.bashrc) — يُعيد تنفيذ الملف في الصدفة الحالية. تسري التغييرات فوراً دون فتح طرفية جديدة.
  • فتح صدفة جديدة — صدفة تسجيل دخول جديدة ستقرأ جميع ملفات الإعداد من الصفر.

في الأتمتة الإنتاجية (مثل مهمة CI تُثبّت أداة ثم تستخدمها في نفس السكريبت)، يجب عليك source للملف أو تعيين المتغير صراحةً — العملية الفرعية الجديدة لا ترث التغييرات التي أجراها السكريبت الأب على ملف لم يُضمَّن بعد.

متغيرات البيئة في الأنظمة الإنتاجية

في البيئات السحابية وبيئات الحاويات، متغيرات البيئة هي الآلية الأساسية لحقن التكوين أثناء التشغيل. منهجية Twelve-Factor App (فلسفة التصميم وراء معظم الخدمات السحابية الحديثة) تُلزم بوضع كل التكوين في البيئة، لا في ملفات التكوين المدمجة في الصورة.

# Dockerfile: لا تُدمج الأسرار في الصورة أبداً # استخدم ARG للقيم في وقت البناء، ENV للافتراضيات في وقت التشغيل FROM ubuntu:24.04 ARG APP_VERSION ENV APP_ENV=production \ LOG_LEVEL=info \ PORT=8080 # docker run: حقن عند التشغيل docker run -e DB_HOST=prod-db.internal \ -e DB_PASSWORD="$(cat /run/secrets/db_pass)" \ -p 8080:8080 \ myapp:latest # Kubernetes: ConfigMap للضبط غير الحساس، Secret لبيانات الاعتماد env: - name: LOG_LEVEL valueFrom: configMapKeyRef: name: app-config key: log_level - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-secret key: password
ممارسة احترافية — لا تطبع الأسرار أبداً: في سكريبتات الصدفة، تجنب echo $DB_PASSWORD أو set -x (التي تطبع كل أمر ومتغيراته الموسَّعة). تسرب الأسرار في سجلات CI من أكثر الحوادث الأمنية شيوعاً في مؤسسات الهندسة. استخدم printenv DB_PASSWORD | wc -c للتحقق من تعيين سر دون الكشف عن قيمته.

تشخيص مشاكل البيئة

حين لا تجد خدمة أو سكريبت ملفاً ثنائياً، أو تفشل في الاتصال بقاعدة البيانات، أو تتصرف بشكل مختلف في CI عن جهازك المحمول، غالباً ما تكون متغيرات البيئة هي السبب. نهج تشخيص منهجي:

# 1. تحقق مما تراه العملية الجارية فعلياً (ليس ما تظن أنك صدّرته) cat /proc/$(pgrep -f myapp)/environ | tr '\0' '\n' | sort # 2. شغّل أمراً في نفس الظروف التي تعمل فيها الخدمة الفاشلة sudo -u deployuser env -i HOME=/home/deployuser PATH=/usr/local/bin:/usr/bin:/bin bash -l -c 'which python3 && python3 --version' # 3. اطبع جميع المتغيرات في أعلى سكريبت فاشل لالتقاط اللقطة env | sort > /tmp/env-snapshot-$(date +%s).txt # 4. تحقق أن توسيع المتغير هو ما تتوقعه (مزلق شائع: مسافات حول =) # خطأ: export MY_VAR = "hello" # bash تعاملها كوسيط أمر # صواب: export MY_VAR="hello" # 5. تحقق إن كان متغير مطلوب غير مُعيَّن أو فارغاً [[ -z "${DB_HOST}" ]] && echo "DB_HOST is unset or empty" && exit 1 # استخدم توسيع المعامل للرجوع إلى قيمة افتراضية: DB_HOST="${DB_HOST:-localhost}"
وضع فشل شائع — مهام cron ووحدات systemd: تُشغّل cron المهام ببيئة شبه فارغة: لا PATH سوى /usr/bin:/bin، ولا ملف إعداد مستخدم مُضمَّن، ولا HOME في بعض الإصدارات. سكريبت يستدعي docker أو terraform بدون PATH كامل سيفشل بصمت. أصلح ذلك إما بتضمين الملف الشخصي في أعلى السكريبت (source /etc/profile) أو باستخدام مسارات مطلقة. وبالمثل، ملفات وحدة systemd لا ترث بيئة المستخدم التفاعلية؛ عيّن توجيهات Environment= أو EnvironmentFile= في كتلة [Service] صراحةً.

المتغيرات الحساسة وآداب الأمان

متغيرات البيئة مرئية لأي شخص يمكنه قراءة /proc/<pid>/environ، وهو مقيّد افتراضياً لمالك العملية و root. لكنها يمكن أن تتسرب عبر:

  • التسجيل المفصّل (مثل set -x في سكريبتات الصدفة، أو middleware التصحيح في أطر الويب)
  • صفحات الخطأ التي تُفرغ البيئة
  • ناتج ps aux إن مُرّر السر كوسيط سطر أوامر بدلاً من متغير بيئة
  • ناتج Docker inspect وأوصاف pod في Kubernetes (لمتغيرات البيئة، ليس الأسرار المُركَّبة كملفات)

لأي شيء حساس حقاً (كلمات المرور، رموز API، المفاتيح الخاصة)، افضّل أنظمة إدارة الأسرار — HashiCorp Vault، أو AWS Secrets Manager، أو Kubernetes Secrets مدعومة بمخزن أسرار مُشفَّر — وأدخلها كملفات في /run/secrets/، لا كمتغيرات بيئة كلما أمكن. كحد أدنى، لا تُودّع ملفات .env أبداً في التحكم في المصدر.