Перейти к основному содержимому

Конфигурация и адаптивные скрипты

Разработчику Архитектору Инженеру
Загрузка PowerShell…

Маршрут
125 — блоки и модули127 — секретыэта статья12 — ошибки.


Зачем выносить данные из кода

Жёстко прошитые пути, списки серверов и пороги в .ps1 заставляют править код при каждом изменении инфраструктуры. Адаптивный скрипт читает данные конфигурации и применяет одну и ту же логику к разным окружениям (dev/stage/prod, филиалы, клиенты).

Разделение:

СлойСодержимоеГде хранить
КодАлгоритм, проверки, обработка ошибок.ps1, .psm1 в Git
КонфигПути, списки, пороги, имена секретов (не сами секреты)JSON/YAML рядом или в share
СекретыПароли, ключи127 — SecretManagement

JSON-конфигурация

Файл config.json рядом со скриптом:

{
"LogFolder": "C:\\App\\logs",
"ArchiveFolder": "D:\\Archives",
"OlderThanDays": 30,
"ArchivePrefix": "logs_",
"SecretNameSql": "SqlMonitor",
"NotifyEmail": "ops@example.com"
}

Загрузка и валидация:

param(
[string]$ConfigPath = (Join-Path $PSScriptRoot 'config.json')
)

if (-not (Test-Path -LiteralPath $ConfigPath)) {
throw "Конфиг не найден: $ConfigPath"
}

$config = Get-Content -LiteralPath $ConfigPath -Raw -Encoding utf8 |
ConvertFrom-Json

foreach ($key in @('LogFolder', 'ArchiveFolder')) {
if (-not $config.$key) { throw "В конфиге отсутствует $key" }
}

$config.OlderThanDays = [int]$config.OlderThanDays
ПрактикаЗачем
$PSScriptRoot для пути к конфигуРаботает из планировщика с -WorkingDirectory
-Encoding utf8Кириллица в путях и письмах
Явная проверка обязательных ключейРанний понятный сбой вместо $null в середине
Отдельный config.prod.jsonПараметр -ConfigPath при деплое

Секреты в JSON не кладут — только имя в SecretStore: "SecretNameSql": "SqlMonitor".


Data-driven функции

Одна функция обрабатывает запись конфигурации — объект или hashtable:

function Test-ServerHealth {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[pscustomobject]$Server
)

$result = [ordered]@{
Name = $Server.Name
Ok = $true
Detail = ''
}

try {
if ($Server.Port) {
$tcp = Test-NetConnection -ComputerName $Server.Host -Port $Server.Port -WarningAction SilentlyContinue
if (-not $tcp.TcpTestSucceeded) { throw "Port $($Server.Port) closed" }
}
}
catch {
$result.Ok = $false
$result.Detail = $_.Exception.Message
}

[pscustomobject]$result
}

Конфиг со списком серверов:

{
"Servers": [
{ "Name": "web-01", "Host": "web-01.corp.local", "Port": 443 },
{ "Name": "db-01", "Host": "db-01.corp.local", "Port": 1433 }
]
}

Оркестратор:

$config = Get-Content "$PSScriptRoot\servers.json" -Raw | ConvertFrom-Json
$report = foreach ($srv in $config.Servers) {
Test-ServerHealth -Server $srv
}
$report | Where-Object { -not $_.Ok } | ForEach-Object { Write-Warning "$($_.Name): $($_.Detail)" }

Классы PowerShell (кратко)

Для стабильной структуры данных в PowerShell 5+ / 7 можно объявить class:

class ServerRecord {
[string]$Name
[string]$HostName
[int]$Port = 443

[bool] TestPort() {
(Test-NetConnection -ComputerName $this.HostName -Port $this.Port -WarningAction SilentlyContinue).TcpTestSucceeded
}
}

$s = [ServerRecord]@{ Name = 'web-01'; HostName = 'web-01.corp.local' }
$s.TestPort()

Классы удобны в модулях с большим числом полей; для простых скриптов достаточно pscustomobject и JSON.


Конфиг внутри модуля

Константы уровня модуля (паттерн из практики работы с SQL через dbatools):

# Inside MyModule.psm1
$_SqlInstance = 'SRV-SQL\SQLEXPRESS'
$_Database = 'PoshAssetMgmt'

function Get-ModuleDatabase {
[CmdletBinding()]
param()
@{ SqlInstance = $_SqlInstance; Database = $_Database }
}

Имена с префиксом $script: или $_ показывают, что это настройки модуля, а не параметры каждого вызова. Для мультисредовых деплоев конфиг модуля генерируют при установке (DSC, Ansible, setup.ps1).


Чего не кладут в конфиг

НеприемлемоАльтернатива
Invoke-Sqlcmd как строка в JSONИмя функции + switch в коде или отдельные ключи Action: "SqlCheck"
Пароль в JSONSecretNameGet-Secret
Произвольный PowerShell в JSONРасширяемый набор известных действий в коде

Конфиг описывает что делать; код — как.


Идempotency и повторный запуск

Скрипт идемпотентен, если повторный запуск не ломает состояние: второй раз создаёт тот же результат, а не дубликаты.

Приёмы:

  • Test-Path / Get-Service перед New-Item / Start-Service;
  • Ensure = 'Present' в DSC — см. 11;
  • возобновляемость с файлом прогресса — 111.

Конфиг vs база данных

JSON / YAMLSQL
Мало записей, редкие правки, один админМного объектов, concurrent update, отчёты
Версионирование в GitЦентральный реестр серверов, CMDB

PowerShell + SQL (модуль dbatools) — для инвентаризации и отчётов; теория SQL — 3.07. Для десятков серверов в JSON хватает; для тысяч — БД или API.


Пример структуры проекта

Deploy-App/
├── Deploy-App.ps1 # оркестратор
├── config.json # пути, флаги (в Git)
├── config.local.json # переопределения (в .gitignore)
└── lib/
└── AppTools.psm1 # функции из [125]

Запуск:

.\Deploy-App.ps1 -ConfigPath .\config.local.json

Чек-лист

#Вопрос
1Секреты только по имени, не по значению в конфиге?
2Конфиг валидируется при старте?
3Повторный запуск безопасен (идемпотентность)?
4Окружения различаются файлом конфига, а не правкой .ps1?
5В JSON нет исполняемого кода?

Дальше

ТемаМатериал
Ошибки, таймауты служб12
Azure Automation + конфиг в переменных11
Возобновляемые длинные задачи111
Pester для конфигафаза 4 — статья 129

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").