Модуль I · IT Skills (Cloud Services & Documents) · тема 6, урок 2 з 2 · 120 хвилин
Сьогодні ти поставиш свій скрипт у розклад. Дашборд почне оновлюватися без тебе — навіть уночі й на канікулах.
А ще ти навчишся ловити автоматику на брехні. Бо ламається вона тихо: сторінка виглядає так само гарно, тільки числа на ній учорашні.
На минулому уроці ти написав ~/bin/build_dash.py: він читає
~/data/responses.csv і кладе метрики в ~/www/dash/data.json.
Поки що цей скрипт запускаєш ти — руками, коли згадаєш. Сьогодні його
запускатиме cron. А ти навчишся бачити, що саме він робить.
Навіть коли тебе немає за комп’ютером.
data.json і зачекає.
Якщо через 16 хвилин файл з’явиться сам і дата всередині буде свіжою —
автоматика працює. Якщо ні — вона не працює, як би переконливо не виглядала
сторінка. Це єдиний чесний доказ.Слова cron, crontab і «лог» поки нічого не означають. Розберемо їх по черзі.
crontab — програма Linux. У PowerShell на твоєму комп’ютері її
немає, і команди з цієї сторінки там не працюють. Локально ти сьогодні
тільки верстаєш index.html і dash.css; усе інше —
у SSH-сесії на своєму акаунті.| Етап | Хв | Що робимо |
|---|---|---|
| Розминка | 10 | Перевірка п’ятого KPI з домашнього завдання |
| Cron: п’ять полів | 20 | Тренажер розкладу і конструктор crontab |
| Логи й повторні запуски | 15 | Симуляція падіння на третьому запуску |
| Практика: запуск за розкладом | 25 | crontab -e, абсолютні шляхи, перший запис у лозі |
| Перерва | 10 | — |
| Свіжість даних | 15 | Плашка, яка червоніє; «Дашборд-детектив» |
| Практика: сторінка дашборда | 20 | 4 картки KPI, 2 графіки, плашка віку даних |
| Тест на автоматизацію | 5 | Учитель видаляє data.json — чекаємо |
| Разом | 120 |
crontab — не рідше ніж раз на 15 хвилин, з абсолютними
шляхами і перенаправленням у лог. Файл ~/logs/dash.log із мітками
часу. Сторінка ~/www/dash/index.html: 4 картки .kpi,
два графіки Chart.js із data.json, плашка «оновлено N хвилин тому».
І стилі до неї — ~/www/dash/dash.css.~/www/dash/.
Скрипт із минулого уроку лишається там, де й був: ~/bin/build_dash.py.Служба, яка щохвилини звіряє час і запускає те, чому час настав
У школі дзвінок на урок дає не вчитель. Є розклад, і дзвінок лунає сам — о 8:30, о 9:25, о 10:20. Ніхто щоразу не натискає кнопку.
На сервері за таке відповідає cron. Це служба, яка працює постійно. Раз на хвилину вона переглядає списки завдань усіх користувачів. І запускає ті рядки, чий розклад збігся з поточною хвилиною.
Твій особистий список зветься crontab — «cron table», таблиця cron.
Кожен рядок crontab — це п’ять полів розкладу. Далі, до кінця рядка, іде команда. Пробіли між полями можуть бути будь-якої довжини. А от полів рівно п’ять. Cron рахує їх від початку рядка. Усе, що після п’ятого, він вважає командою.
crontab -e відкриває твій особистий список у редакторі.
Після збереження cron одразу перечитує його. Перезапускати нічого не треба.
А crontab -l друкує список на екран, нічого не змінюючи.
crontab -r видаляє твій crontab без жодного запитання.
Клавіші e і r поруч. Перше, що робиш після налаштування:
crontab -l > ~/notes/crontab.bak — тоді відновлення займе
одну команду, а не пів уроку.Замість п’яти полів можна написати скорочення: @daily,
@hourly, @weekly, @monthly,
@yearly. Наприклад, @daily — це рівно
0 0 * * *, тобто опівночі.
Окремо стоїть @reboot. Це не розклад, а «один раз після
старту сервера». Для дашборда він не підходить: сервер може не
перезавантажуватися місяцями.
Однокласник хотів «щодня о 7:30» і написав 7 30 * * *.
Скрипт не спрацював о 7:30, а crontab -e при збереженні
вилаявся. Що сталося?
У чужому crontab ти бачиш рядок 0 12 13 * 5. Автор каже:
«це щоб раз на рік у п’ятницю 13-го надіслати вітання». Коли він
спрацює насправді?
*/15Найпоширеніша хибна модель: «кожні 15 хвилин від моменту, коли я зберіг»
Скісна риска — це крок усередині діапазону поля, а не таймер.
Запис */15 у полі хвилин читається так. Візьми весь діапазон
0–59. Залиш кожне п’ятнадцяте значення. Виходить 0, 15, 30, 45.
Ці хвилини прибиті до годинника, а не до тебе.
Зберіг crontab о 10:07? Перший запуск буде о 10:15. Не о 10:22. А зберіг о 10:14 — запуск уже через хвилину. Cron не знає й не хоче знати, коли ти натиснув «зберегти».
*/7 хвилини
йдуть 0, 7, 14, 21, 28, 35, 42, 49, 56 — і далі знову 0. Червона ділянка —
той самий проміжок: між :56 і :00 минає не 7 хвилин, а 4. Тому кроки
беруть такі, що ділять 60 націло: 5, 10, 15, 20, 30.| Запис | Коли спрацює | Скільки разів на добу |
|---|---|---|
* * * * * | щохвилини | 1440 |
*/15 * * * * | о :00, :15, :30, :45 кожної години | 96 |
0 * * * * | щогодини рівно о нулях | 24 |
*/5 9-17 * * 1-5 | кожні 5 хв з 9:00 до 17:59 у робочі дні | 108 |
30 7 * * * | щодня о 7:30 | 1 |
0 6 * * 1 | щопонеділка о 6:00 | 1 на тиждень |
5 0 1 * * | першого числа кожного місяця о 0:05 | 1 на місяць |
О 10:07 ти зберіг */15 * * * *. Дивишся на годинник
о 10:20 — у лозі один запис. Об 11:00 — скільки їх буде всього?
Крути п’ять полів — сторінка перекладе розклад людською мовою і покаже п’ять наступних запусків справжніми датами
Правило перевірки просте. Спробуй прочитати свій рядок вголос однією фразою. Не виходить — він, найімовірніше, робить не те, що ти думаєш. Тож спочатку формулюй фразу, а потім складай поля.
Наступні п’ять запусків від цієї хвилини:
Готовий рядок для crontab -e —
з абсолютними шляхами і перенаправленням у лог:
| Частина | Навіщо саме так |
|---|---|
/usr/bin/python3 |
абсолютний шлях до інтерпретатора. Просто python3 може не знайтися: у cron свій, дуже короткий PATH |
/home/student/bin/build_dash.py |
абсолютний шлях до скрипта. ~/bin/… залежить від оболонки, bin/… — від поточної теки |
>> |
дописати в кінець файлу. Одна риска > щоразу стирала б лог, і ти б бачив тільки останній запуск |
2>&1 |
відправити помилки туди ж, куди й звичайний вивід. Без цього лог буде порожній саме тоді, коли він найпотрібніший |
% — спецсимвол: cron перетворює його на
перенесення рядка і все, що після першого %, віддає команді
на вхід. Тому date +%Y-%m у crontab розвалюється. Кожен відсоток
екранують зворотним слешем: date +\%Y-\%m. У самому скрипті
на Python цієї проблеми немає — вона тільки в рядку crontab.Дашборд має оновлюватися кожні 10 хвилин, але тільки в шкільний час. Це з 8:00 до 16:59 у робочі дні. Який рядок правильний?
Причина майже завжди одна: cron працює в іншому оточенні
Коли ти набираєш команду в SSH-сесії, її виконує твоя оболонка.
Перед стартом вона багато чого встигла. Прочитала ~/.bashrc.
Зібрала довгий PATH. Підхопила мову й віртуальне середовище
Python. І стоїть вона в тій теці, де стоїш ти.
Cron не робить нічого з цього. Він не входить у систему, не читає твоїх
конфігів, не є інтерактивною оболонкою. Він бере /bin/sh,
дає йому кілька змінних — і все.
PATH у cron задає сама служба; у більшості
дистрибутивів це /usr/bin:/bin. Перевіряти напам’ять не треба —
нижче є спосіб подивитися своє.Не вір таблиці, вір своєму серверу. Постав на одну хвилину рядок, який вивалить усе оточення у файл. Зачекай хвилину. Потім прибери цей рядок.
Точний список у твоєму акаунті може відрізнятися — важливо не запам’ятати рядки, а побачити, наскільки він коротший за твій.
| Правило | Погано | Добре |
|---|---|---|
| Абсолютний шлях до програми | python3 build_dash.py |
/usr/bin/python3 /home/student/bin/build_dash.py |
| Абсолютні шляхи всередині скрипта | open('data/responses.csv') |
open(Path.home()/'data'/'responses.csv') |
| Свій вивід — у свій файл | нічого не перенаправлено | >> /home/student/logs/dash.log 2>&1 |
Абсолютний шлях до python3 легко дізнатися: which python3
друкує саме те, що треба підставити. Абсолютний шлях до свого скрипта —
readlink -f ~/bin/build_dash.py.
open('dash/data.json', 'w'), файл спокійно
створиться в /home/student/dash/data.json. Скрипт завершиться
кодом 0, у лозі буде «OK», а сторінка місяцями показуватиме старі числа.
Зелений лог не доводить, що дані оновилися — доводить тільки дата у файлі,
який реально читає сторінка.Cron не викидає те, що надрукувала твоя команда. Він намагається
надіслати це поштою власникові завдання. Адресу можна змінити
рядком MAILTO= на початку crontab.
На навчальному сервері поштова служба зазвичай не налаштована. Вивід тихо зникає. Звідси й відчуття «нічого не сталося». Насправді сталося — просто повідомлення пішло в нікуди.
Вручну ~/bin/build_dash.py відпрацьовує за пів секунди.
За розкладом — нічого: data.json не оновлюється,
а ~/logs/dash.log порожній, у ньому взагалі жодного рядка.
З чого почати?
Лог заповнюється справно: кожні 15 хвилин «START», потім
«OK, 52 записи». Але сторінка дашборда третій день показує ті самі
числа. І поле generated_at у ній не змінюється. Де шукати?
Скрипт, який мовчить, неможливо ні перевірити, ні полагодити
Коли ти запускаєш програму руками, ти бачиш її вивід. Коли її запускає cron, ти не бачиш нічого. Скільки вона працювала? Скільки записів обробила? Чи впала взагалі? Відповідей немає.
Лог — це твій єдиний спосіб дізнатися, що відбувалося о 3:15 ночі.
Скрипту не треба вміти писати у файл. Йому досить print().
Перенаправлення >> у рядку crontab саме складе вивід
у файл.
Зате кожен рядок мусить мати мітку часу. Без неї лог — купа однакових речень, з якої нічого не дізнаєшся.
Тут важливі три дрібниці.
Перша — flush=True. Без неї рядок зависає в буфері до кінця
роботи, а у файл потрапляє з запізненням.
Друга — raise наприкінці. Завдяки їй скрипт завершується
з ненульовим кодом, і система це бачить.
Третя — мітка часу з часовим поясом. Хвостик +03:00
рятує, коли ти порівнюєш лог із часом на своєму комп’ютері.
З цих шести рядків одразу видно, що сталося. О 7:45 щось прибрало CSV. О 8:00 усе відновилося. Без міток часу було б видно тільки «іноді ERROR».
| Писати | Не писати ніколи |
|---|---|
| час початку і час завершення | паролі, токени, ключі доступу |
| скільки записів прочитано й скільки відкинуто | самі відповіді однокласників |
| куди саме записано результат — повний шлях | імена, класи, будь-які персональні дані |
| тип помилки й текст винятку | увесь вміст CSV «про всяк випадок» |
| скільки секунд тривала робота | вміст запитів із чужими даними |
~/logs/ — приватна тека, як ~/data/. Лог у публічній
теці означає, що будь-хто в інтернеті прочитає, коли твій скрипт падав
і які шляхи є в тебе на сервері.96 запусків на добу × 2 рядки × 70 байтів — це близько 4,9 МБ на рік. Небагато.
Але коли скрипт падає, Python друкує в лог не один рядок, а цілу «драбинку». У ній видно, у якому файлі й рядку сталася біда і через які функції код туди дійшов. Ця драбинка зветься traceback. Одна така займає десять-двадцять рядків.
Кілька сотень падінь — і файл виростає до десятків мегабайтів.
Редактор такий файл уже не відкриє, а tail працюватиме
повільно.
Ротація — це коли лог розрізають на частини, а старі частини прибирають.
Найпростіша ротація для учня — писати одразу в помісячний файл. Пам’ятай про екранування відсотків у crontab:
Другий рядок раз на тиждень видаляє логи, яких не чіпали понад 90 днів.
У «дорослих» системах те саме робить служба logrotate. Вона
стискає старі файли й тримає задану кількість поколінь.
Сторінка дашборда може прийти по data.json рівно в ту мить,
коли скрипт його переписує. І отримати половину файлу.
Лікується це одним прийомом. Пиши в тимчасовий файл, а потім перейменовуй його. У межах однієї файлової системи перейменування миттєве — його називають атомарним. Тому в будь-який момент існує або старий файл цілком, або новий цілком.
Ідемпотентність — це коли повторний запуск не псує результат. Наш скрипт ідемпотентний: він щоразу перераховує метрики з нуля й перезаписує файл.
А якби він дописував точки в series? Два запуски
підряд подвоїли б дані. І графік почав би брехати.
У лозі одного учня рядки такі:
2026-09-02T08:00:01 обробляю: Марія Коваленко, 9-Б, «іноді».
І так по одному на кожного респондента. Лог лежить у приватній
~/logs/. Що з цим не так?
Учень поставив у crontab > /home/student/logs/dash.log
з однією рискою. Скрипт працює вже тиждень. Що він побачить у лозі?
Застарілий дашборд гірший за відсутній — бо йому вірять
Подивись на пакет молока. Там завжди стоїть дата — коли розлито і до якого числа воно придатне. Без цієї дати ти не знаєш, пити його чи вилити.
З даними так само. Якщо дашборд не відкривається, людина йде шукати їх деінде. А якщо відкривається і виглядає бездоганно? Тоді вона ухвалює рішення за цифрами тижневої давнини. І навіть не здогадується, що щось не так.
Відсутність даних чесна. Стара цифра без попередження — ні.
Відповідь на це — одне поле у файлі. Зветься воно
generated_at. Скрипт ставить його в момент, коли порахував
метрики. Сторінка порівнює це поле з поточним часом і показує різницю.
Це єдина цифра на дашборді, яку не можна ані підробити випадково, ані «забути оновити».
"2026-09-02T07:30:04", браузер прочитає це як
місцевий час читача. Учень із іншого поясу побачить «оновлено 3 години
тому» на цілком свіжих даних. Тому в Python пишуть
datetime.now().astimezone().isoformat(timespec='seconds') —
зсув +03:00 потрапляє у файл сам.Файл data.json лежить поруч зі сторінкою, у теці
~/www/dash/. Але сам собою він у сторінку не потрапить.
HTML не вміє читати файли. Це вміє тільки JavaScript.
Уяви відвідувача бібліотеки. Він підходить до віконця, називає назву книжки й чекає. Через хвилину йому виносять. Та сама дія в браузері зветься fetch — з англійської «сходи й принеси».
Чекати доводиться завжди: файл лежить на сервері, а не в пам'яті браузера. Тому код пишуть у два кроки. Спершу попросили, потім дочекалися.
Розберемо ці рядки. fetch('data.json') просить у сервера
файл за його адресою. res.json() перетворює отриманий текст
на об'єкт, у якого вже можна питати d.metrics.responses_total.
Слово await означає «зачекай тут, поки не принесуть».
Без нього код побіжить далі з порожніми руками й намалює порожній
дашборд.
Хвостик ?v= із числом — прийом проти кешу. Браузер любить
показувати вчорашню копію файлу, якщо адреса та сама. Змінне число робить
адресу щоразу новою, і браузер таки йде на сервер.
data.json — і тільки звідти.Плашка рахує вік від generated_at, а не від моменту,
коли сторінка відкрилася. Це різні речі. Сторінку можна відкрити о будь-якій
годині — дані від цього свіжішими не стануть.
оновлено 6 хвилин тому
Чотири картки, під ними плашка. Числа тут ілюстративні —
у тебе будуть свої, з data.json.
Чотири картки показують стан на зараз. Але «48 відповідей» нічого не каже про те, росте це чи падає. Тому поруч ставлять графік: він показує ту саму метрику в часі.
Креслити осі, сітку й підписи руками довго. Готова бібліотека, яка робить це за тебе, зветься Chart.js. Ти даєш їй список підписів і список чисел — вона малює лінію, підписує осі й показує значення, коли навести курсор.
Бібліотека — це один файл із чужим кодом. Він уже лежить на сервері,
у /opt/school/vendor/chartjs/: качати нічого не треба, ти
просто скопіюєш його до себе в ~/www/dash/assets/. Команду
дає крок практики.
Місце під сам графік розмічають тегом <canvas>.
Це порожній прямокутник, усередині якого JavaScript має право малювати.
Тепер сама діаграма. Функція draw(d) — та сама, яку
викликав load() після fetch.
Читається це так. type — яким графіком малювати:
'line' для змін у часі, 'bar' для порівняння
величин між собою.
labels — підписи по горизонталі, у нас це дати.
datasets — самі числа, які лягають на графік.
.map() проходить по масиву series з твого
data.json і витягає з кожної точки одне поле. Із записів
виду {"date":"2026-08-27","responses":5} виходить окремо
список дат і окремо список чисел. Рівно те, чого чекає Chart.js.
Другий <canvas> роби стовпчиками:
type: 'bar' по інструментах. У labels підуть
їхні назви, у data — скільки разів кожен назвали. Обидва
графіки читають той самий data.json, який щочверть години
перезаписує cron.
chart.min.js — звичайний текст, і сервер приймає його
по FTP без заперечень.Плашка на дашборді бадьоро повідомляє «оновлено 2 хвилини тому».
А ти щойно бачив, що data.json не змінювався три дні.
Відкриваєш код і бачиш const t0 = Date.now() на початку
скрипта. Що відбувається?
Твій скрипт зламався в п’ятницю ввечері, ти помітив це в понеділок. Дашборд усі вихідні показував п’ятничні числа й виглядав ідеально. Що зробити в першу чергу — ще до того, як лагодити скрипт?
Одна модельна доба: коли спрацьовує скрипт, що з’являється в лозі та якого кольору стає плашка
Перемикач «зламати» не пише розпливчасте «щось не спрацювало». Він
показує точні рядки, які ти побачиш у своєму ~/logs/dash.log.
Найважливіший перемикач — 2>&1. Вимкни його й
подивись, як лог стає порожнім саме тоді, коли все ламається.
Числа в рядках лога («48 записів», «0.31 c») ілюстративні — вони показують форму запису, а не твої дані.
У симуляторі вибери «відносний шлях у скрипті». Подивись на лог і на плашку одночасно. Яка комбінація там виходить і чому вона найнебезпечніша?
Чотири вузли ланцюжка — і по одній команді на кожен
Ланцюжок від cron до сторінки короткий. Зламатися він може рівно в чотирьох місцях. Проходь їх по черзі зліва направо й не перескакуй. 80% часу причина знаходиться на першому або другому кроці.
Такий вивід закриває питання одразу: розкладу немає. Найчастіша причина
проста. Редактор запитав «зберегти?», а учень натиснув «ні». Друга причина —
crontab -e виконали від іншого користувача.
Файл є, розмір нуль. Отже, cron рядок виконав — інакше файл би
не створився. Але вивід у лог не потрапив. Перше, що перевіряєш, —
чи є в рядку 2>&1.
Запускати руками в своїй сесії безглуздо: там усе працює, ти це вже знаєш.
Треба відтворити бідне оточення cron. Команда env -i
запускає програму з порожнім набором змінних:
Різниця між двома рядками — і є діагноз. Перший показує рівно те, що бачив cron. Другий доводить, що з абсолютними шляхами все працює.
Сам cron теж пише про кожен запуск. Але пише він у системний журнал, а не до тебе в теку. На частині серверів звичайний користувач туди не має доступу, і це нормально:
Такий рядок доводить головне: cron команду запустив. Якщо рядок є, а результату немає — проблема всередині скрипта. Тоді шукай у своєму лозі, а не в розкладі.
У /var/log/syslog видно рядки CRON … CMD (…build_dash.py)
кожні 15 хвилин. У ~/logs/dash.log — жодного рядка за сьогодні,
файл не порожній, але останній запис учорашній. Про що це говорить?
Кроки 1—11 — у SSH-сесії на сервері, кроки 12—13 — у твоєму редакторі, крок 14 — по FTP, кроки 15—16 — знову на сервері. Галочки зберігаються, сторінку можна закрити.
student — це не твій логін. Дізнайся свій
через whoami і заміни в усіх абсолютних шляхах. Скопійований
без правки рядок або не працюватиме, або (гірше) писатиме в чужу теку.Головний тест не читає твій код. Він видаляє data.json
і чекає, чи повернеться файл сам.
Демонстраційний режим: результат згенеровано для показу. На сервері
ця кнопка викликає POST /api/check, який запускає
check 12 від імені учня. Тест самовідновлення триває до 16 хвилин —
запускай його не в останню хвилину уроку.
~/www/dash/data.json у резервну копію
й засікає час. Далі він раз на хвилину перевіряє, чи файл з’явився. Якщо
за 16 хвилин файл повернувся і generated_at у ньому не старший
за 20 хвилин — критерій зараховано. Створити файл руками в цей момент не
вийде: сервер бачить, о котрій він з’явився і чи збігається час усередині.crontab -l запускається просто зараз — у рядку абсолютні
шляхи, інтервал не рідший за 15 хвилин і 2>&1;tail -f ~/logs/dash.log лишається відкритим, і ви разом
дочікуєтеся живого запуску;generated_at на вчорашній і перезавантажує
сторінку — плашка має почервоніти;~/logs/, а не в ~/www/,
і що саме в нього не потрапляє.Це те, що бачить учитель, коли виставляє оцінку.
| Складник | Вага | Результат |
|---|
*/15 прибито
до годинника, а не до моменту, коли ти зберіг файл;2>&1 лог буде порожній саме тоді, коли він потрібен;generated_at, і показують його
читачеві — застарілий дашборд гірший за відсутній.data.json створено руками — тест самовідновлення це ловить;generated_at;% у рядку crontab не екранували — команда обірвалася на ньому.Додай у build_dash.py обробку помилки. Обгорни читання CSV
у try/except. Запиши в лог рядок ERROR з типом
винятку. І перевір, що дашборд при цьому не ламається. Для перевірки
тимчасово зроби порожній CSV:
Правильна поведінка виглядає так. У лозі з’являється ERROR.
Старий data.json залишається недоторканим, а не перетворюється
на порожній. А плашка на сторінці поступово жовтіє й червоніє.
Опиши цю поведінку трьома реченнями в
~/www/dash/README.md.
Синтаксис cron, поведінка оточення й формат мітки часу — з документації та стандартів. Перевіряти дозволено й корисно.
/,
правило «або» для дня місяця й дня тижня, спецрядки @daily
і @reboot, змінна MAILTO, значення
%.
manpages.debian.org — crontab(5)crontab: ключі -e, -l,
-r, редактор із EDITOR.
manpages.debian.org — crontab(1)cron: перевірка завдань щохвилини, оточення
завдання, SHELL=/bin/sh, PATH=/usr/bin:/bin,
робоча тека — домашня.
manpages.debian.org — cron(8)CRON_TZ.
github.com/cronie-crond/croniecrontab і базове оточення
завдання.
pubs.opengroup.org — crontab>, >>,
дублювання дескриптора 2>&1.
gnu.org — Bash Manual, Redirectionsenv: ключ -i для запуску з порожнім
оточенням.
gnu.org — coreutils, envlogrotate — системна ротація журналів.
manpages.debian.org — logrotate(8)datetime.isoformat, astimezone,
os.replace як атомарне перейменування.
docs.python.org — datetime,
docs.python.org — os.replaceDate.parse і розбір рядків ISO 8601 у браузері.
MDN — Date.parseПовний список із поясненнями, що звідки взято, —
у файлі urok-12-джерела.md поруч із цією сторінкою.