Конфигурация приложений на Go (Golang): от переменных окружения до сложных YAML-структур
Конфигурация — один из ключевых аспектов любого production-приложения. В Go (Golang) экосистема предлагает гибкие и мощные инструменты для управления настройками, адаптированные под разные сценарии: от простых 12-factor приложений до микросервисов с десятками параметров. В этой статье мы разберём основные подходы, популярные библиотеки и лучшие практики конфигурирования Go-приложений.
Зачем нужна отдельная конфигурация?
Жёсткое кодирование параметров (hardcode) — антипаттерн. Конфигурация позволяет:
- Изменять поведение приложения без перекомпиляции;
- Разделять настройки для разных окружений (dev/staging/prod);
- Обеспечивать безопасность (секреты не попадают в репозиторий);
- Упрощать развёртывание в контейнерах и облачных платформах.
Базовый подход: переменные окружения
Стандартная библиотека Go предоставляет функцию os.Getenv. Это минималистичный, но эффективный способ для небольших проектов, соответствующий принципам 12-factor app.
package main
import (
"fmt"
"os"
)
type Config struct {
Port string
DBHost string
DBUser string
}
func loadConfig() Config {
return Config{
Port: getEnv("APP_PORT", "8080"),
DBHost: getEnv("DB_HOST", "localhost"),
DBUser: getEnv("DB_USER", "admin"),
}
}
func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}
func main() {
cfg := loadConfig()
fmt.Printf("Server starting on port %s\n", cfg.Port)
}
Библиотека Viper: «швейцарский нож» конфигурации
Viper (от создателей Cobra) — самая популярная библиотека для конфигурации в Go. Она поддерживает множество форматов, поиск в нескольких источниках и hierarchy (иерархию) настроек.
Основные возможности Viper:
- Чтение из YAML, JSON, TOML, HCL, INI, envfile, Java properties;
- Поддержка переменных окружения с кастомными префиксами;
- Установка значений по умолчанию;
- Динамическое обновление конфигурации (live reload);
- Интеграция с удалёнными источниками (etcd, Consul, Firebase).
Пример использования Viper с YAML:
package main
import (
"fmt"
"github.com/spf13/viper"
)
type AppConfig struct {
Server ServerConfig `mapstructure:"server"`
Database DatabaseConfig `mapstructure:"database"`
}
type ServerConfig struct {
Port int `mapstructure:"port"`
Host string `mapstructure:"host"`
}
type DatabaseConfig struct {
URL string `mapstructure:"url"`
Pool int `mapstructure:"pool"`
}
func main() {
viper.SetConfigName("config") // имя файла без расширения
viper.SetConfigType("yaml") // формат
viper.AddConfigPath(".") // путь поиска
viper.AddConfigPath("./config/")
// Чтение переменных окружения с префиксом APP_
viper.AutomaticEnv()
viper.SetEnvPrefix("APP")
if err := viper.ReadInConfig(); err != nil {
panic(fmt.Errorf("fatal error config file: %s", err))
}
var cfg AppConfig
if err := viper.Unmarshal(&cfg); err != nil {
panic(fmt.Errorf("unable to decode into struct: %s", err))
}
fmt.Printf("Server: %s:%d\n", cfg.Server.Host, cfg.Server.Port)
fmt.Printf("Database URL: %s\n", cfg.Database.URL)
}
Пример файла config.yaml:
server:
port: 8080
host: "0.0.0.0"
database:
url: "postgres://user:pass@localhost:5432/app"
pool: 10
Библиотека envconfig: простота и декларативность
Если вам не нужны сложные форматы, а только переменные окружения — envconfig (github.com/kelseyhightower/envconfig) идеальное решение. Она автоматически маппит переменные окружения на поля структуры с помощью тегов.
package main
import (
"fmt"
"github.com/kelseyhightower/envconfig"
)
type Config struct {
Port int `envconfig:"PORT" default:"8080"`
DBUrl string `envconfig:"DATABASE_URL" required:"true"`
Debug bool `envconfig:"DEBUG" default:"false"`
Timeout int `split_words:"true" default:"30"` // превратится в TIMEOUT
}
func main() {
var cfg Config