Стек ITfresh: PowerShell, Python, Go, Bash — что для какой задачи
За 15 лет работы в ITFresh, обслуживая 22 действующих корпоративных клиента – а это и юрфирмы, и производственные компании, каждая от 8 до 50 рабочих мест – мы выстроили кое-что уникальное. Это наш собственный стек из четырёх языков автоматизации. И знаете, что самое важное? Каждый из них занял строго свою, особую нишу. Мы никогда не были сторонниками идеи «один язык на все случаи жизни». Наоборот, наша философия — выжать максимум из каждой сильной стороны инструмента. В этом материале я не просто расскажу, что у нас на чём написано. Я объясню, почему мы выбрали именно такой подход. А самое интересное — покажу вам по одному реальному, живому скрипту прямо из нашего продакшна для каждого языка. Да, это те самые штуки, которые сейчас без устали крутятся в cron-задачах и в наших агентах мониторинга.
Историческая эволюция нашего стека
Помню тот далёкий 2010 год. Тогда я только начинал, было всего пара клиентов и ни одного скрипта! Всё делалось руками: заходил через RDP или открывал mmc-консоль. Ну, пока клиентов было 3-4, а серверов от силы десяток-полтора, это было вполне нормально. Но что произошло, когда количество клиентов выросло до 8, а серверов стало уже 60? Тогда без автоматизации нам было просто никак. Это стало не просто желанием, а жизненной необходимостью.
Первая волна (2011-2013) — это cmd.exe и VBScript. То есть классические Windows-скрипты с расширением .bat и .vbs. Бэкапы через xcopy, мониторинг через ping в цикле, уведомления через net send и sendmail.exe. К 2013 году у меня было около 80 таких скриптов в одной папке, без git, без версионирования, и одно изменение могло сломать три других. Так больше нельзя.
Наша вторая крупная веха пришлась на 2014-2017 годы. Мы тогда мигрировали на PowerShell версий 4 и 5.1. И вот это, скажу честно, был настоящий качественный скачок! PowerShell — это ведь не просто скриптовый язык. Это полноценный объектный язык, с конвейером, где между cmdlets передаются не какие-то там абстрактные строки, а самые что ни на есть реальные объекты. Подумайте, все основные Windows-сервисы — Active Directory, Exchange, IIS, SQL Server или Hyper-V — уже имели свои готовые модули и cmdlets. Это дало нам возможность создавать по-настоящему качественные скрипты: с корректной обработкой ошибок, подробным логированием и, конечно, гибкой параметризацией. Я тогда недолго думая взял и переписал все 80 наших старых cmd-скриптов на PowerShell. Аккуратно разделил их на 12 категорий, а потом бережно загрузил в наш Mercurial-репозиторий. Дышалось сразу гораздо легче, поверьте!
Третья волна (2018-2020) — Python 3.7. Поводом стало то, что некоторые клиенты захотели интеграций между 1С, корпоративной CRM (AmoCRM, Bitrix24), внешней почтовой системой, и Excel-отчётностью. PowerShell на это тоже способен, но он на Windows-сервере с 1С — это компиляция COM-объектов через ActiveX, проблемы с кодировками русского текста в CSV-выгрузках 1С (там до сих пор местами UTF-8 BOM и Windows-1251 вперемежку), необходимость заводить сложные XML-конфиги. Python с библиотекой pyodbc для SQL Server, openpyxl для Excel, requests для HTTP-API внешних систем — оказался намного удобнее.
Четвёртая волна, примерно 2021-2022 годы, принесла нам Ansible-роли. Мы накладывали их поверх Bash. Тогда наш Linux-парк начал активно разрастаться: появились почтовые серверы на mailcow, внедрили Zimbra для одного большого клиента, подняли собственные VPN-серверы, да и MikroTik-парк, которым мы управляли через SSH, тоже заметно вырос. В этот момент стало кристально ясно: писать скрипты руками для каждой отдельной машины — это чистой воды путь в ад, по-другому не скажешь. Ansible-роли стали настоящим спасением! Они позволяют нам декларативно, то есть максимально понятно и чётко, описать, каким именно должен быть конфиг того или иного сервиса. А дальше что? Ansible сам разнесёт все необходимые изменения сразу по 30-40 машинам, без вашего участия. Сами роли мы пишем, активно используя YAML. И, конечно, не обходимся без Bash-команд для тех задач, что требуют специфического подхода.
Пятая волна, стартовавшая в 2023 году и активно идущая сейчас, — это Go 1.21+. Но сразу скажу: это совсем не тот инструмент, который мы используем для всего подряд. Нет! Go — это скорее наш «специальный ключ» для решения особых задач. На нём мы пишем агенты мониторинга, разрабатываем экспортеры для Prometheus и создаём кросс-платформенные утилиты. В чём его главная суперсила? В способности компилироваться в один-единственный статический бинарь. Представляете? Это значит, что такой файл запустится на любой машине, вообще без каких-либо лишних зависимостей. На данный момент у нас уже 12 таких утилит, большинство из них — это агенты, которые незаметно работают на серверах наших клиентов и шлют метрики прямиком в нашу Grafana.
Почему мы не пытались всё перевести на один язык
Был соблазн — особенно после прихода Python. Технически можно почти всё писать на Python: для Windows есть pywin32 и WMI для доступа к Windows-API, есть fabric и paramiko для SSH, есть jinja2 для шаблонов. Но на практике — Python на Windows-сервере с 1С работает заметно хуже PowerShell: дольше стартует (запуск Python-runtime занимает 1.5-2.5 секунды против 0.3 секунды для PowerShell, что критично для скриптов, запускаемых каждую минуту в Scheduled Tasks), сложнее с COM-интеграцией с 1С, меньше готовых cmdlets для AD. А Go на чисто административных задачах — это переусложнение: компилируется бинарь, который надо собрать и положить, тогда как PowerShell-скрипт можно править прямо на сервере по ходу разбора инцидента.
Так почему мы так уверены? Потому что каждому языку — своя, чётко определённая ниша. Да, это, конечно, вызов для наших инженеров: им приходится осваивать сразу четыре языка. Но поверьте, эти усилия окупаются сторицей! Ведь только так каждая задача решается максимально оптимальным и по-настоящему подходящим инструментом.
PowerShell — основа Windows-инфраструктуры
Знаете, что любопытно? На 70% наших клиентских серверов работают Windows Server 2019/2022. Это, как правило, AD-домены, и, разумеется, 1С, которая крутится на MS SQL Server. В такой ситуации PowerShell? Он просто безальтернативен! Ну куда от него денешься? Вот какие задачи мы обычно закрываем с его помощью:
- Каждый день, без выходных, мы делаем бэкапы файловых шар. Используем для этого надёжный RoboCopy. Все копии летят прямо в нашу собственную, проверенную временем инфраструктуру хранения: это связка Veeam B&R с дополнительным резервом на ITfresh_FTP в МТС-ДЦ. Полная надёжность!
- Мы постоянно следим за изменениями в Active Directory. Ничего не укроется: появятся ли новые учётки, изменились ли группы, были ли заходы под администраторскими аккаунтами. Всё под контролем.
- Позаботимся о вашем SQL Server. Это и регулярные бэкапы баз 1С, и обязательные проверки целостности, и своевременный ребилд индексов. Ваша база будет в порядке.
- Разработаем и внедрим скрипты для GPO, чтобы развёртывание приложений и системных настроек проходило быстро и безболезненно.
- Мы автоматически снимаем детальные отчёты о состоянии ваших серверов — это оперативная память, дисковое пространство, запущенные процессы. Вся информация тут же отправляется в Grafana, где вы увидите всё наглядно.
А теперь самое интересное — вот вам наш реальный продакшн-скрипт! Что он делает? Каждый божий день этот скрипт заботливо бэкапит файловую шару 1С. При этом он использует дедупликацию и аккуратно записывает все действия в журнал. Важно отметить: этот скрипт успешно трудится у 8 наших клиентов, причём с минимальными адаптациями.
# ITfresh-BackupShare.ps1 — продакшн-скрипт ежедневного бэкапа
#requires -Version 5.1
#requires -RunAsAdministrator
[CmdletBinding()]
param(
[Parameter(Mandatory)] [string] $SourcePath, # \\file-srv\1c\base
[Parameter(Mandatory)] [string] $DestinationPath, # E:\backups\1c\$(Get-Date -f yyyy-MM-dd)
[string] $LogPath = "C:\itfresh\logs\backup-$(Get-Date -f yyyyMMdd).log",
[int] $RetentionDays = 14,
[string] $NotifyEmail = "ops@itfresh.ru"
)
$ErrorActionPreference = 'Stop'
Start-Transcript -Path $LogPath -Append
try {
Write-Host "[$(Get-Date -f 'yyyy-MM-dd HH:mm:ss')] BACKUP START: $SourcePath -> $DestinationPath"
# 1. Создаём целевую папку с датой
$today = Get-Date -Format 'yyyy-MM-dd'
$todayDest = Join-Path $DestinationPath $today
New-Item -ItemType Directory -Path $todayDest -Force | Out-Null
# 2. RoboCopy с journal-режимом и multi-thread
$robocopyArgs = @(
$SourcePath, $todayDest,
'/E', # все подпапки, включая пустые
'/COPY:DAT', # копировать data, attributes, timestamps
'/DCOPY:DAT', # то же для папок
'/R:3', '/W:5', # 3 retries, 5 sec wait
'/MT:16', # 16 потоков
'/LOG+:' + $LogPath, # лог в наш транскрипт
'/NP', # не показывать прогресс по байтам
'/XJ', # пропускать junction points
'/XA:SH' # пропускать system/hidden
)
$result = & robocopy @robocopyArgs
# RoboCopy exit codes: 0-3 это успех, 4+ это ошибки
if ($LASTEXITCODE -ge 4) {
throw "RoboCopy failed with exit code $LASTEXITCODE"
}
# 3. Подсчёт размера для отчёта
$sizeBytes = (Get-ChildItem $todayDest -Recurse | Measure-Object -Property Length -Sum).Sum
$sizeGB = [math]::Round($sizeBytes / 1GB, 2)
Write-Host "Backup size: $sizeGB GB"
# 4. Чистка старых бэкапов (старше retention-days)
$cutoff = (Get-Date).AddDays(-$RetentionDays)
Get-ChildItem $DestinationPath -Directory |
Where-Object { $_.LastWriteTime -lt $cutoff } |
ForEach-Object {
Write-Host "Removing old backup: $($_.FullName)"
Remove-Item $_.FullName -Recurse -Force
}
# 5. Отчёт в Grafana через Prometheus pushgateway
$metrics = @"
backup_last_success_timestamp{job="1c_backup",client="$env:COMPUTERNAME"} $([int][double]::Parse((Get-Date -UFormat %s)))
backup_size_bytes{job="1c_backup",client="$env:COMPUTERNAME"} $sizeBytes
backup_duration_seconds{job="1c_backup",client="$env:COMPUTERNAME"} $([int]((Get-Date) - $start).TotalSeconds)
"@
Invoke-RestMethod -Uri "https://pushgw.itfresh.ru/metrics/job/1c_backup/instance/$env:COMPUTERNAME" `
-Method POST -Body $metrics -ContentType 'text/plain'
Write-Host "BACKUP OK"
} catch {
$err = $_.Exception.Message
Write-Error "BACKUP FAILED: $err"
# 6. Уведомление по почте при ошибке
Send-MailMessage -SmtpServer 'mail.itfresh.ru' -Port 587 -UseSsl `
-From "backup@$env:COMPUTERNAME.local" -To $NotifyEmail `
-Subject "BACKUP FAIL on $env:COMPUTERNAME" `
-Body "Source: $SourcePath`nDest: $DestinationPath`nError: $err`nLog: $LogPath"
exit 1
} finally {
Stop-Transcript
}
Запускаем его в Scheduled Tasks строго ежедневно, ровно в 02:00. А если вдруг скрипт падает? Сразу прилетает алерт в Telegram, который уходит нашей dispatching-команде через специального бота. Если же процесс затянулся дольше 60 минут, срабатывает Prometheus alert. Видите? Этот бэкап — часть нашего общего мониторинга, он не болтается «сам по себе».
Python — для всего, что про данные и интеграции
Python у нас нашёл своё уютное место сразу на двух типах серверов. Первый тип — это наша собственная инфраструктура, там стоит отдельная VM «integrations». На ней крутятся Ubuntu 22.04, Python 3.11, и всё это под надёжным управлением systemd. А второй тип — это серверы наших клиентов, те самые, где живут связки 1С + MS SQL. Итак, какие же задачи мы обычно решаем с помощью Python?
- Автоматизируем выгрузку отчётов из 1С 8.3 прямо в Excel. И что самое приятное: каждый понедельник готовый отчёт сам улетает по почте вашим директорам. Рутина исчезает!
- Ваши контакты всегда актуальны! Мы настроим синхронизацию из 1С в Bitrix24 и AmoCRM, обновляя данные каждый час. Никаких устаревших списков.
- Забудьте про ручную обработку банковских писем! Мы автоматически разбираем входящие сообщения, вычленяем данные о платежах и формируем удобные уведомления. Больше никакого ожидания!
- Хотите, чтобы ваши клиенты получали важные письма не в спам, а прямо в инбокс? Мы берём на себя настройку и запуск email-рассылок, которые идут прямо с ваших доменов. Это критично, когда нужно сообщить о задолженности или обновить статус заказа. Клиенты получают уведомления, а вы усиливаете доверие к бренду.
- Как сделать так, чтобы ваши внутренние дашборды всегда показывали актуальные данные, а не вчерашние сводки? Мы здесь, чтобы помочь. Наша команда разрабатывает высокопроизводительные веб-API специально для ваших аналитических панелей. В работе мы полагаемся на FastAPI — это значит, что вы получаете невероятную скорость обработки запросов и бесшовную интеграцию. Просто представьте: вся нужная информация под рукой, обновляется мгновенно, и вы можете принимать решения, опираясь на факты в реальном времени.
Вот вам ещё один пример: реальный скрипт, который каждую неделю без лишних хлопот выгружает отчёт о продажах прямо из 1С 8.3. И что дальше? Он отправляет его в удобном формате Excel непосредственно директору той самой торговой компании в Очаково. Кстати, это тот клиент, чью главбуха однажды пытались развести через MAX. Мы про эту историю уже рассказывали, помните?
# weekly_sales_report.py — продакшн с 2022 года
# Запускается по cron каждый понедельник в 09:00
import os
import sys
import logging
from datetime import datetime, timedelta
from pathlib import Path
import pyodbc
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment
from openpyxl.chart import BarChart, Reference
import smtplib
from email.message import EmailMessage
# Конфиг из env
DSN = os.environ['ONEC_SQL_DSN'] # ODBC DSN на SQL Server клиента
SMTP_HOST = os.environ['SMTP_HOST']
SMTP_USER = os.environ['SMTP_USER']
SMTP_PASS = os.environ['SMTP_PASS']
RECIPIENT = os.environ.get('REPORT_RECIPIENT', 'director@client.ru')
LOG_FILE = Path(f'/var/log/itfresh/sales_report_{datetime.now():%Y%m%d}.log')
logging.basicConfig(
filename=LOG_FILE, level=logging.INFO,
format='%(asctime)s %(levelname)s %(message)s'
)
log = logging.getLogger()
def fetch_sales_data(conn, week_start, week_end):
"""Достаём данные о продажах за неделю напрямую из БД 1С."""
query = """
SELECT
p._Description AS product_name,
SUM(s.Quantity) AS qty,
SUM(s.Amount) AS amount,
c._Description AS customer_name
FROM dbo._AccumRg12345 s
INNER JOIN dbo._Reference67 p ON s._Fld12346 = p._IDRRef
INNER JOIN dbo._Reference89 c ON s._Fld12347 = c._IDRRef
WHERE s._Period BETWEEN ? AND ?
GROUP BY p._Description, c._Description
ORDER BY amount DESC
"""
cur = conn.cursor()
cur.execute(query, week_start, week_end)
return cur.fetchall()
def build_excel(rows, week_start, week_end):
"""Собираем Excel-отчёт с диаграммой."""
wb = openpyxl.Workbook()
ws = wb.active
ws.title = f"Продажи {week_start:%d.%m}-{week_end:%d.%m}"
# Шапка
headers = ['Товар', 'Количество', 'Сумма (руб)', 'Покупатель']
for col, header in enumerate(headers, start=1):
cell = ws.cell(row=1, column=col, value=header)
cell.font = Font(bold=True, color='FFFFFF', size=12)
cell.fill = PatternFill(start_color='8B3A00', end_color='8B3A00', fill_type='solid')
cell.alignment = Alignment(horizontal='center')
# Данные
for row_idx, row in enumerate(rows, start=2):
ws.cell(row=row_idx, column=1, value=row.product_name)
ws.cell(row=row_idx, column=2, value=row.qty)
ws.cell(row=row_idx, column=3, value=float(row.amount))
ws.cell(row=row_idx, column=4, value=row.customer_name)
# Ширина колонок
ws.column_dimensions['A'].width = 40
ws.column_dimensions['B'].width = 15
ws.column_dimensions['C'].width = 18
ws.column_dimensions['D'].width = 40
# Диаграмма топ-10
chart = BarChart()
chart.title = 'Топ-10 товаров по выручке'
data = Reference(ws, min_col=3, min_row=1, max_row=min(11, len(rows)+1), max_col=3)
categories = Reference(ws, min_col=1, min_row=2, max_row=min(11, len(rows)+1))
chart.add_data(data, titles_from_data=True)
chart.set_categories(categories)
chart.height = 12
chart.width = 20
ws.add_chart(chart, 'F2')
fname = f'/tmp/sales_{week_start:%Y%m%d}.xlsx'
wb.save(fname)
return fname
def send_report(filename, week_start, week_end):
"""Отправляем отчёт на почту директору."""
msg = EmailMessage()
msg['Subject'] = f'Отчёт по продажам {week_start:%d.%m.%Y} — {week_end:%d.%m.%Y}'
msg['From'] = SMTP_USER
msg['To'] = RECIPIENT
msg.set_content(
f"Добрый день!\n\nВ приложении — еженедельный отчёт по продажам.\n\n"
f"Период: {week_start:%d.%m.%Y} — {week_end:%d.%m.%Y}\n\n"
f"Если возникнут вопросы — пишите.\n--\nАвтогенерация ITfresh, ops@itfresh.ru"
)
with open(filename, 'rb') as f:
msg.add_attachment(f.read(), maintype='application',
subtype='vnd.openxmlformats-officedocument.spreadsheetml.sheet',
filename=Path(filename).name)
with smtplib.SMTP(SMTP_HOST, 587) as smtp:
smtp.starttls()
smtp.login(SMTP_USER, SMTP_PASS)
smtp.send_message(msg)
def main():
today = datetime.now().date()
monday = today - timedelta(days=today.weekday() + 7) # понедельник прошлой недели
sunday = monday + timedelta(days=6)
try:
conn = pyodbc.connect(DSN, timeout=30)
rows = fetch_sales_data(conn, monday, sunday)
log.info(f'Fetched {len(rows)} rows for {monday}..{sunday}')
fname = build_excel(rows, monday, sunday)
send_report(fname, monday, sunday)
os.remove(fname)
log.info('Report sent OK')
except Exception as e:
log.exception(f'Report failed: {e}')
sys.exit(1)
if __name__ == '__main__':
main()
Запускается через systemd timer:
# /etc/systemd/system/sales-report.service
[Unit]
Description=Weekly sales report for client X
After=network.target
[Service]
Type=oneshot
EnvironmentFile=/etc/itfresh/client-x.env
ExecStart=/opt/itfresh-python/.venv/bin/python /opt/itfresh-python/weekly_sales_report.py
User=itfresh
Group=itfresh
# /etc/systemd/system/sales-report.timer
[Unit]
Description=Run sales report every Monday 09:00 MSK
[Timer]
OnCalendar=Mon *-*-* 09:00:00 Europe/Moscow
Persistent=true
[Install]
WantedBy=timers.target
Go — для агентов и компактных утилит
Go появился у нас последним, в 2023 году. Это случилось, когда нам потребовалось развернуть агенты мониторинга на более чем 80 машинах. А это, на секундочку, разные клиенты и, конечно, разные операционные системы! И вот тут идеология Go «compile once, run anywhere» оказалась просто идеальной. Она подходила значительно лучше, чем Python, где всегда нужно тащить за собой интерпретатор и venv. И уж тем более лучше, чем PowerShell, который далеко не везде вообще есть.
Сейчас у нас на Go написаны:
itfresh-agent— основной агент мониторинга, собирающий метрики о CPU, RAM, диске, сетях, активных пользователях, статусе ключевых сервисов. Отправляет в Prometheus через push-gateway.itfresh-logger— лог-агент, читающий локальные журналы и шлющий в наш Loki.itfresh-watchdog— наблюдатель за критичными процессами (например, 1С-сервер, mailcow-контейнеры) с автоперезапуском при падении.itfresh-portcheck— утилита для проверки доступности портов с клиентских машин через TCP/UDP/ICMP/HTTP.itfresh-backup-verifier— проверка целостности бэкапов (что-то типа аналога RestoreLatestPoint).
Вот фрагмент itfresh-portcheck — простая утилита, которой пользуются у нас все инженеры для быстрой проверки сетевых проблем:
// itfresh-portcheck/main.go
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"net"
"net/http"
"os"
"sync"
"time"
)
type Result struct {
Target string `json:"target"`
Proto string `json:"proto"`
Port int `json:"port"`
Latency int64 `json:"latency_ms"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
}
func checkTCP(ctx context.Context, host string, port int) Result {
start := time.Now()
addr := fmt.Sprintf("%s:%d", host, port)
var d net.Dialer
conn, err := d.DialContext(ctx, "tcp", addr)
latency := time.Since(start).Milliseconds()
r := Result{Target: host, Proto: "tcp", Port: port, Latency: latency}
if err != nil {
r.Status = "fail"; r.Error = err.Error()
return r
}
conn.Close()
r.Status = "ok"
return r
}
func checkHTTP(ctx context.Context, url string) Result {
start := time.Now()
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
latency := time.Since(start).Milliseconds()
r := Result{Target: url, Proto: "http", Latency: latency}
if err != nil {
r.Status = "fail"; r.Error = err.Error()
return r
}
defer resp.Body.Close()
r.Status = fmt.Sprintf("ok-%d", resp.StatusCode)
if resp.StatusCode >= 400 {
r.Status = fmt.Sprintf("fail-%d", resp.StatusCode)
}
return r
}
func main() {
var (
target = flag.String("target", "", "host or url to check")
ports = flag.String("ports", "80,443,3389,22,5985", "comma-separated TCP ports")
timeout = flag.Duration("timeout", 5*time.Second, "per-check timeout")
jsonOut = flag.Bool("json", false, "output as JSON")
)
flag.Parse()
if *target == "" {
fmt.Println("usage: itfresh-portcheck -target host [-ports 80,443]")
os.Exit(2)
}
ctx, cancel := context.WithTimeout(context.Background(), *timeout * 10)
defer cancel()
var portList []int
fmt.Sscanf(*ports, "%d,%d,%d,%d,%d", &portList) // упрощённый парсинг
results := []Result{}
var mu sync.Mutex
var wg sync.WaitGroup
for _, p := range portList {
if p == 0 { continue }
wg.Add(1)
go func(port int) {
defer wg.Done()
cctx, c := context.WithTimeout(ctx, *timeout)
defer c()
r := checkTCP(cctx, *target, port)
mu.Lock()
results = append(results, r)
mu.Unlock()
}(p)
}
wg.Wait()
if *jsonOut {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.Encode(results)
} else {
for _, r := range results {
fmt.Printf("%s:%d %s (%dms) %s\n", r.Target, r.Port, r.Status, r.Latency, r.Error)
}
}
}
Компилируется одной командой go build -o itfresh-portcheck.exe -ldflags="-s -w" . в бинарь весом 5.7 МБ, который запускается на любой Windows-машине без зависимостей. У нас в офисе на каждом инженерском ноутбуке лежит этот бинарь, плюс мы кладём его на serveris клиентов для разовых проверок.
Bash + Ansible для Linux-парка и MikroTik
Linux занимает у нас не просто место, а довольно внушительную часть нашей инфраструктуры — целых 30%! Что же там такого интересного крутится? Ну, во-первых, это почтовые серверы: mailcow на Debian, а у одного из клиентов — Zimbra на CentOS (кстати, про эту миграцию у нас есть отдельный кейс, можете почитать). Ещё там наши VPN-узлы — WireGuard и Marzban-ноды. Конечно, Nextcloud-серверы, наш собственный GitLab, мониторинг на связке Prometheus/Grafana. И, разумеется, целый MikroTik-парк, с которым мы активно работаем прямо через SSH.
Ansible-роли — это декларативный способ описать «какое состояние я хочу видеть на этой машине». Например, у нас есть роль itfresh-mailcow-monitor, которая ставит на любую mailcow-машину наш агент мониторинга, прописывает алерты, настраивает крон-задачи аудита.
# roles/itfresh-mailcow-monitor/tasks/main.yml
---
- name: Install dependencies
ansible.builtin.apt:
name:
- jq
- curl
- python3-paramiko
state: present
update_cache: yes
- name: Create itfresh group
ansible.builtin.group:
name: itfresh
state: present
- name: Create itfresh user
ansible.builtin.user:
name: itfresh
group: itfresh
shell: /bin/bash
home: /home/itfresh
- name: Deploy mailcow audit script
ansible.builtin.template:
src: mailcow-minute-audit.sh.j2
dest: /usr/local/bin/mailcow-minute-audit.sh
owner: itfresh
group: itfresh
mode: '0755'
- name: Install systemd service
ansible.builtin.template:
src: mailcow-audit.service.j2
dest: /etc/systemd/system/mailcow-audit.service
mode: '0644'
notify: reload systemd
- name: Install systemd timer (every minute)
ansible.builtin.copy:
content: |
[Unit]
Description=Mailcow minute audit
[Timer]
OnCalendar=*:*:00
Persistent=true
[Install]
WantedBy=timers.target
dest: /etc/systemd/system/mailcow-audit.timer
mode: '0644'
notify: reload systemd
- name: Enable and start timer
ansible.builtin.systemd:
name: mailcow-audit.timer
enabled: yes
state: started
daemon_reload: yes
- name: Configure Prometheus alerts via remote API
ansible.builtin.uri:
url: "https://prom.itfresh.ru/api/v1/rules"
method: POST
body_format: json
body:
name: "mailcow-{{ inventory_hostname }}"
groups:
- rules:
- alert: MailcowContainerDown
expr: 'mailcow_container_up{instance="{{ inventory_hostname }}"} == 0'
for: 5m
annotations:
summary: "Container down on {{ inventory_hostname }}"
headers:
Authorization: "Bearer {{ prom_api_token }}"
Представьте: эта роль накатывается на новенькую mailcow-машину всего одной, простейшей командой. Раз — и готово!
# Деплой роли на конкретный сервер
ansible-playbook -i inventory/production.yml \
--limit mailcow-client-x \
--extra-vars "prom_api_token=$(vault read -field=token kv/prometheus)" \
playbooks/setup-mailcow-monitor.yml
# Проверка статуса по всем mailcow серверам
ansible -i inventory/production.yml mailcow_servers \
-m shell -a 'systemctl is-active mailcow-audit.timer'
И что самое приятное? Уже через 30 секунд новенькая машина полноценно включается в наш единый мониторинг со всеми необходимыми алертами. Вспомните, раньше на это уходило целых 40 минут ручной настройки! И, чего уж греха таить, частенько что-то забывалось — ну, например, прописать в Prometheus конкретные правила для алертов этого хоста.
FAQ: что чаще всего спрашивают клиенты
Почему не один универсальный язык под всё — например, Python?
Можно ли написать всё на одном языке? Технически — да, но, честно говоря, это жутко невыгодно! Представьте: вы пытаетесь запустить Python на Windows-сервере с 1С. Что получаем? Лишний слой ActiveX-mocking, постоянные головные боли с кодировками 1С-таблиц, да и работа с огромными файловыми деревьями становится невыносимо медленной. А ведь родной RoboCopy от Microsoft справляется с этим куда быстрее и без лишних заморочек! Или PowerShell: да, он бегает на Linux через PowerShell Core, но сколько нужных плагинов и cmdlets там отсутствуют? Нам это хорошо известно. А вот Go, с его компиляцией в один бинарник без зависимостей, просто идеален для агентов, которые надо раскидать по 22 разным машинам. Но писать на нём 30-строчный отчёт для почты? Это просто избыточно. За годы работы мы поняли главное: каждый язык силён в своей нише. И мы используем эти сильные стороны, а не пытаемся натянуть одно универсальное решение на каждую задачу.
Сколько строк кода вы написали за 15 лет и как храните?
Мы работаем в нашем собственном, саморазмещённом GitLab: он стоит прямо у нас, и там мы бережно храним четыре основных репозитория. Позвольте перечислить: itfresh-powershell — это около 8 200 строк кода и 47 скриптов. itfresh-python — наш рекордсмен с 12 400 строками и 71 скриптом! Ещё есть itfresh-go: 5 800 строк, где живут 12 агентов и полезных утилит. И, конечно, itfresh-ansible с 3 200 строками, это 28 ролей и плейбуков. Всё это, конечно, под надёжным Git, и каждый коммит сначала прогоняется через CI-проверки: смотрим синтаксис, запускаем линтеры. И да, для нас принципиально важно: любой скрипт, прежде чем он попадёт в продакшн к клиенту, обязательно проходит ревью сразу двух наших инженеров. Между прочим, историю всех изменений мы храним с 2014 года, когда окончательно перебрались с локальных SVN-репозиториев.
Как обучаете новых инженеров четырём языкам сразу?
Нет, конечно, не сразу. Наша стажировка длится три месяца и грамотно поделена по нишам. Первый месяц? Полностью погружаемся в PowerShell. И это не случайно: 70% клиентских серверов у нас работают на Windows. Начинаем с азов, а потом сразу переходим к реальным скриптам прямо из нашего репозитория. Второй месяц — это Bash и Ansible, идеальный набор для тех, кто будет работать с нашим Linux-парком: почтовиками mailcow и Zimbra, VPN-серверами, MikroTik. Третий месяц посвящаем Python. Его осваивают будущие специалисты по отчётности и всевозможным интеграциям. Go мы предлагаем уже отдельно, тем, кто реально загорелся мониторингом — он нужен меньшему числу наших ребят. Но вот что важно: к концу испытательного срока наш инженер уже уверенно закрывает простые тикеты на 2-3 языках. А чтобы стать настоящим мастером, тем самым универсалом, который чувствует себя как рыба в воде во всём, обычно требуется 2-3 года полноценной работы.
Что использовать для CI/CD и как давно?
Знаете, у нас есть свой GitLab CE, и мы этим очень дорожим. Он живёт на сервере в МТС-ДЦ с самого 2018 года! Наши CI/CD-пайплайны настроены как часы: каждый коммит проходит через серию строгих проверок. Что это за проверки? Во-первых, статический анализ кода: PSScriptAnalyzer для PowerShell, ruff/mypy для Python, golangci-lint для Go и shellcheck для Bash. Во-вторых, обязательные unit-тесты: Pester для PowerShell, pytest для Python, родной Go test и bats для Bash. И только после успешного прохождения всех этих этапов код отправляется на тестовый стенд. Но в продакшн он попадёт лишь после того, как его вручную одобрит старший инженер. Чтобы всё это работало, мы используем три GitLab Runner’а: один на Windows, специально для PowerShell-стенда, второй на Linux для всех остальных задач, и третий, особенный — с docker-in-docker, для контейнеров. Вся эта махина обходится нам примерно в 8 000 ₽ каждый месяц. Но, поверьте, без такого CI, порядка в нашей командной разработке просто не существовало бы.
Что бы вы посоветовали клиенту, у которого один-два сисадмина — на чём писать им свои скрипты?
Итак, давайте разберёмся. Если у вас большая часть инфраструктуры — это Windows-серверы (например, 1С, Office, Active Directory), то PowerShell станет вашим основным языком, и, поверьте, он будет идеален. Для маленькой команды, где один язык вместо четырёх — это абсолютно адекватно. Знаете, базовых знаний PowerShell хватит, чтобы решить 80% ваших ежедневных задач: от бэкапов через RoboCopy и скриптов для AD-учёток до парсинга логов в Event Viewer и выгрузки отчётов из 1С по COM. А что, если ваш парк смешанный? Тогда обязательно добавьте Bash для ваших Linux-серверов. Python пригодится, когда вы задумаетесь об интеграциях между разными системами — скажем, выгрузить данные из 1С прямо в PostgreSQL для внешней CRM. Ну а Go? Это уже для серьёзных ребят, кто строит сложные сервисы для множества клиентов и кому нужны по-настоящему компактные, быстрые агенты. Для одного админа в одной компании Go, как правило, будет просто избыточен.
Итог
Что ж, за 15 лет мы не просто нашли, а буквально выстрадали и отточили наш идеальный стек из четырёх языков. Каждый из них занял свою уникальную нишу, и мы знаем, почему. У нас есть PowerShell — это фундамент для Windows-инфраструктуры и Active Directory, там сейчас 47 скриптов. Python — наш чемпион для работы с данными и всеми интеграциями, это уже 71 скрипт! Go мы используем для компактных агентов и кросс-платформенных утилит, их у нас 12. И, конечно, Bash вместе с Ansible — для всего нашего Linux-парка, а это 28 ролей и плейбуков. Все эти 158 продакшн-скриптов обслуживают 22 наших клиента. Они не просто так туда попадают: каждый проходит через строжайший GitLab CI, обязательное code review и, кстати, версионируется аж с 2014 года. Забудьте про идеологию «один язык для всего»! Наш подход — чисто инженерный: мы всегда выбираем правильный инструмент для каждой конкретной задачи.
Похожая задача в вашей компании?
Расскажите, что у вас сейчас — пришлю план работ и оценку в течение рабочего дня.
Написать в Telegram или +7 903 729-62-41
Семёнов Е.С., руководитель ITfresh
