Хабрахабр

Как дебажить переменные окружения в Linux

Часто бывает так, что приходишь на машину и обнаруживаешь какой-то скрипт, запущенный под системным пользователем неделю назад. Кто его запустил? Где искать этот run.php? Или добавляешь запись в /etc/crontab, а скрипт там падает с ошибкой «command not found». Почему? И что делать? 

У меня есть ответы на эти вопросы.

Переменные окружения

Практически во всех современных операционных системах у процессов существуют переменные окружения. Технически они представляют собой набор именованных строк. Если запускается подпроцесс, то он автоматически наследует копию окружения родителя. 

Среди прочих есть переменная PATH, которая указывает пути для поиска исполняемых файлов, переменная HOME, которая указывает на домашнюю директорию пользователя, переменные, отвечающие за языковые предпочтения пользователя, и многие другие. 

Восполним этот пробел. Есть множество обзоров, описывающих значения этих переменных, а вот статей о том, как расследовать проблемы, практически нет.

Кто запустил процесс?

Итак, мы обнаружили скрипт, запущенный под системным пользователем неделю назад. Кто его запустил? Зачем? Может, про него просто забыли? Запустить его потенциально могли человек 10–15, всех не опросишь. Как найти, кто же это был? И где лежит этот run.php?

$ ps x | grep run.php
10684 ? Ss 472:25 /local/php/bin/php run.php

На помощь приходят переменные окружения процесса и особенность sudo. Есть такая переменная PWD, в которой оболочка хранит текущую рабочую директорию; это значение, по сути, сохраняет информацию о текущей директории в момент запуска команды. Также утилита sudo по умолчанию оставляет в переменной окружения процесса информацию о том, из-под какого пользователя была запущена она сама. 

Вуаля: Переменные окружения (и многое другое) для любого запущенного процесса можно посмотреть в /proc.

$ cat /proc/10684/environ | tr '\0' '\n' | grep SUDO_USER
SUDO_USER=alexxz
$ cat /proc/10684/environ | tr '\0' '\n' | grep PWD
PWD=/home/etlmaster

Кхм, сам и запустил. Ну с кем не бывает?.. 

В общем, вот таким нехитрым методом в простых ситуациях можно найти информацию о процессе, которая в общем случае недоступна.

Скрипт работает из командной строки, но не работает из cron

Одним из случаев, когда приходится вспоминать о переменных окружения, является ситуация, когда добавленный в /etc/crontab скрипт падает с ошибкой. Заходишь на сервер по SSH, запускаешь команду, всё вроде работает как надо. А при автоматическом запуске показывает что-то типа «hive: command not found». 

В таких случаях разработчики выкручиваются кто как может. Вообще есть хорошая практика прописывать полный путь до исполняемых команд, однако это не всегда возможно. Более опытные оборачивают свою команду в bash -l. Кто-то добавляет нужный путь в PATH частью команды в кронтабе. Всё так: сделал, добавил в мониторинг и забыл. А наученные горьким опытом крон-бомбы ещё и flock довернуть не забывают.

Да, задача решена. После таких манипуляций в душе настоящего инженера остаётся некий осадочек. Чем один подход лучше другого? Но я же ни фига не понял, что происходит! Где все эти настройки хранятся и кем меняются?

Логируем вывод команды env из крона и своё текущее окружение: Давайте сравним переменные окружения, которые есть у процесса, когда он запускается из крона, и переменные окружения, которые есть у нас в командной строке.

$ echo "* * * * * env > ~/crontab.env" | crontab; sleep 60; echo "" | crontab;
$ env > my.env

Смотрим, что там в переменной PATH:

> grep ^PATH= crontab.env my.env
Crontab.env:
PATH=/usr/bin:/bin
My.env:
PATH=/local/hive/bin:/local/python/bin:/local/hadoop/bin:/local/hadoop/bin:/local/hive/bin:/local/hadoop/bin:/usr/local/bin:/usr/bin:/bin

Так там под кроном только самый минимум! Мама мия! Конечно же, надо подгружать нормальные переменные окружения. 

Давайте посмотрим, какое окружение будет, если добавить bash -l:

$ echo "* * * * * bash -l env > ~/crontab.env" | crontab; sleep 60; echo "" | crontab; alexxz@bi1.mlan:~> grep ^PATH= crontab.env my.env
Crontab.env:
PATH=/local/hive/bin:/local/python/bin:/local/hadoop/bin:/local/hadoop/bin:/local/hadoop/bin:/local/hive/bin:/usr/local/bin:/usr/bin:/bin
My.env:
PATH=/local/hive/bin:/local/python/bin:/local/hadoop/bin:/local/hadoop/bin:/local/hive/bin:/local/hadoop/bin:/usr/local/bin:/usr/bin:/bin

Разница уже не так заметна. Все пути представлены. Некоторые в другом порядке, некоторые повторяются, но это уже намного лучше, чем было. Остальные переменные тоже неплохо настроены. Есть, конечно, небольшая разница в локали, в переменных от SSH, но это уже не должно драматично влиять на работу скрипта. 

И, конечно же, не забываем про flock. Теперь понятно, почему bash -l просто необходим в crontab-записях.

Отлаживаем инициализацию логин-скриптов

Проблема вроде бы решена, всё из крона работает. Но как же получается, что некоторые пути дублируются в переменной PATH? Значит, есть какой-то беспорядок в настройке сервера. Давайте попробуем разобраться. 

Какой-то бесконечный поток условий про какие-то особые случаи архитектур, терминалов и невероятно важных настроек цветов для команды ls. Открываем какой-нибудь ман по инициализации окружения, вычитываем, какие скрипты и в каком порядке выполняются, с воодушевлением начинаем пробегать их глазами — и через несколько минут приходит чувство отчаяния. Нас интересует одна чёртова переменная PATH! Боль, отчаяние, ненависть!

Знакомьтесь: На самом деле всё несколько проще.

env -i bash -x -l -c 'echo 123' > login.log 2>&1

Что делает эта команда? Создаёт новый процесс bash с девственно чистым окружением, указывает, что надо запустить скрипты инициализации и всё подробно залогировать в файле login.log. Теперь у нас есть возможность не выполнять в уме все скрипты, а просто прочитать, что, где и когда выполнилось и откуда появилась та или иная настройка окружения.

Там всё почти тривиально. Я не буду детально разбирать, как читать получившийся лог. Да, где-то перемудрили админы при настройке пакетов в паппете. Упомяну лишь, что одно попадание у меня оказалось из /etc/profile и два — из /etc/bash.bashrc. Мне работать это не мешает. Ну ничего, я им расскажу, что именно не так на сервере, а они уж сами решат, надо это чинить или нет.

Зато теперь я знаю и умею!

S. P. В совсем сложных случаях и чтобы разобраться вообще во всём, можно обернуть команду в strace:

strace -f env -i bash -x -l -c 'echo 123' > login.log 2>&1

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть